Browse Source

Merge pull request #2101 from MediaBrowser/beta

Beta
Luke 8 years ago
parent
commit
ba9577f380
78 changed files with 964 additions and 824 deletions
  1. 10 38
      Emby.Drawing/GDI/DynamicImageHelpers.cs
  2. 5 3
      Emby.Drawing/GDI/GDIImageEncoder.cs
  3. 8 6
      MediaBrowser.Api/Library/FileOrganizationService.cs
  4. 2 1
      MediaBrowser.Api/Movies/MoviesService.cs
  5. 99 51
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  6. 0 5
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  7. 0 2
      MediaBrowser.Api/Playback/StreamRequest.cs
  8. 0 2
      MediaBrowser.Api/Reports/ReportsService.cs
  9. 13 0
      MediaBrowser.Api/Sync/SyncHelper.cs
  10. 2 1
      MediaBrowser.Api/Sync/SyncService.cs
  11. 0 2
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  12. 0 20
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  13. 0 1
      MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
  14. 18 4
      MediaBrowser.Controller/Entities/BaseItem.cs
  15. 1 0
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  16. 5 16
      MediaBrowser.Controller/Entities/Folder.cs
  17. 25 21
      MediaBrowser.Controller/Entities/TV/Episode.cs
  18. 6 2
      MediaBrowser.Controller/Entities/TV/Season.cs
  19. 23 1
      MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs
  20. 1 0
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  21. 6 1
      MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs
  22. 9 0
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  23. 9 3
      MediaBrowser.Controller/Session/ISessionManager.cs
  24. 5 5
      MediaBrowser.Dlna/PlayTo/Device.cs
  25. 1 1
      MediaBrowser.Dlna/PlayTo/PlayToController.cs
  26. 0 1
      MediaBrowser.Dlna/Profiles/DefaultProfile.cs
  27. 0 2
      MediaBrowser.Dlna/Profiles/KodiProfile.cs
  28. 7 0
      MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
  29. 75 43
      MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
  30. 4 0
      MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
  31. 8 8
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  32. 2 0
      MediaBrowser.Model/Configuration/EncodingOptions.cs
  33. 3 0
      MediaBrowser.Model/Configuration/LibraryOptions.cs
  34. 2 0
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  35. 0 3
      MediaBrowser.Model/Configuration/UserConfiguration.cs
  36. 5 1
      MediaBrowser.Model/Dlna/AudioOptions.cs
  37. 1 2
      MediaBrowser.Model/Dlna/DeviceProfile.cs
  38. 26 22
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  39. 1 3
      MediaBrowser.Model/Dlna/StreamInfo.cs
  40. 0 3
      MediaBrowser.Model/Dlna/TranscodingProfile.cs
  41. 2 1
      MediaBrowser.Model/Entities/MetadataProviders.cs
  42. 6 0
      MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs
  43. 1 1
      MediaBrowser.Model/Net/MimeTypes.cs
  44. 0 4
      MediaBrowser.Model/Querying/ItemFilter.cs
  45. 6 3
      MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
  46. 1 1
      MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
  47. 1 1
      MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
  48. 6 3
      MediaBrowser.Providers/Photos/PhotoProvider.cs
  49. 7 10
      MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs
  50. 2 1
      MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs
  51. 16 0
      MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
  52. 68 0
      MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs
  53. 94 6
      MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs
  54. 18 2
      MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
  55. 21 19
      MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
  56. 0 6
      MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  57. 2 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  58. 1 1
      MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
  59. 1 2
      MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
  60. 6 3
      MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
  61. 9 2
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  62. 5 0
      MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs
  63. 48 0
      MediaBrowser.Server.Implementations/Sync/SyncNotificationEntryPoint.cs
  64. 71 323
      MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
  65. 4 25
      MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
  66. 1 2
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  67. 1 2
      MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
  68. 0 40
      MediaBrowser.Server.Startup.Common/Migrations/CollectionGroupingMigration.cs
  69. 0 40
      MediaBrowser.Server.Startup.Common/Migrations/FolderViewSettingMigration.cs
  70. 97 0
      MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs
  71. 38 26
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  72. 26 20
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  73. 9 0
      MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
  74. 6 0
      MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
  75. 3 1
      MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
  76. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  77. 1 1
      Nuget/MediaBrowser.Common.nuspec
  78. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 10 - 38
Emby.Drawing/GDI/DynamicImageHelpers.cs

@@ -33,7 +33,9 @@ namespace Emby.Drawing.GDI
                     graphics.SmoothingMode = SmoothingMode.HighQuality;
                     graphics.SmoothingMode = SmoothingMode.HighQuality;
                     graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                     graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                     graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                     graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
-                    graphics.CompositingMode = CompositingMode.SourceCopy;
+
+                    // SourceCopy causes the image to be blank in OSX
+                    //graphics.CompositingMode = CompositingMode.SourceCopy;
 
 
                     for (var row = 0; row < rows; row++)
                     for (var row = 0; row < rows; row++)
                     {
                     {
@@ -44,19 +46,9 @@ namespace Emby.Drawing.GDI
 
 
                             if (files.Count > index)
                             if (files.Count > index)
                             {
                             {
-                                using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
+                                using (var imgtemp = Image.FromFile(files[index]))
                                 {
                                 {
-                                    using (var memoryStream = new MemoryStream())
-                                    {
-                                        fileStream.CopyTo(memoryStream);
-
-                                        memoryStream.Position = 0;
-
-                                        using (var imgtemp = Image.FromStream(memoryStream, true, false))
-                                        {
-                                            graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
-                                        }
-                                    }
+                                    graphics.DrawImage(imgtemp, x, y, cellWidth, cellHeight);
                                 }
                                 }
                             }
                             }
 
 
@@ -90,7 +82,9 @@ namespace Emby.Drawing.GDI
                     graphics.SmoothingMode = SmoothingMode.HighQuality;
                     graphics.SmoothingMode = SmoothingMode.HighQuality;
                     graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                     graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                     graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                     graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
-                    graphics.CompositingMode = CompositingMode.SourceCopy;
+
+                    // SourceCopy causes the image to be blank in OSX
+                    //graphics.CompositingMode = CompositingMode.SourceCopy;
 
 
                     for (var row = 0; row < rows; row++)
                     for (var row = 0; row < rows; row++)
                     {
                     {
@@ -99,21 +93,10 @@ namespace Emby.Drawing.GDI
                             var x = col * singleSize;
                             var x = col * singleSize;
                             var y = row * singleSize;
                             var y = row * singleSize;
 
 
-                            using (var fileStream = fileSystem.GetFileStream(files[index], FileMode.Open, FileAccess.Read, FileShare.Read, true))
+                            using (var imgtemp = Image.FromFile(files[index]))
                             {
                             {
-                                using (var memoryStream = new MemoryStream())
-                                {
-                                    fileStream.CopyTo(memoryStream);
-
-                                    memoryStream.Position = 0;
-
-                                    using (var imgtemp = Image.FromStream(memoryStream, true, false))
-                                    {
-                                        graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
-                                    }
-                                }
+                                graphics.DrawImage(imgtemp, x, y, singleSize, singleSize);
                             }
                             }
-
                             index++;
                             index++;
                         }
                         }
                     }
                     }
@@ -121,16 +104,5 @@ namespace Emby.Drawing.GDI
                 }
                 }
             }
             }
         }
         }
-
-        private static Stream GetStream(Image image)
-        {
-            var ms = new MemoryStream();
-
-            image.Save(ms, ImageFormat.Png);
-
-            ms.Position = 0;
-
-            return ms;
-        }
     }
     }
 }
 }

+ 5 - 3
Emby.Drawing/GDI/GDIImageEncoder.cs

@@ -119,9 +119,11 @@ namespace Emby.Drawing.GDI
                         thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
                         thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
                         thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
                         thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
                         thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
                         thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
-                        thumbnailGraph.CompositingMode = !hasPostProcessing ?
-                            CompositingMode.SourceCopy :
-                            CompositingMode.SourceOver;
+
+                        // SourceCopy causes the image to be blank in OSX
+                        //thumbnailGraph.CompositingMode = !hasPostProcessing ?
+                        //    CompositingMode.SourceCopy :
+                        //    CompositingMode.SourceOver;
 
 
                         SetBackgroundColor(thumbnailGraph, options);
                         SetBackgroundColor(thumbnailGraph, options);
 
 

+ 8 - 6
MediaBrowser.Api/Library/FileOrganizationService.cs

@@ -154,9 +154,12 @@ namespace MediaBrowser.Api.Library
 
 
         public void Post(PerformOrganization request)
         public void Post(PerformOrganization request)
         {
         {
+            // Don't await this
             var task = _iFileOrganizationService.PerformOrganization(request.Id);
             var task = _iFileOrganizationService.PerformOrganization(request.Id);
 
 
-            Task.WaitAll(task);
+            // Async processing (close dialog early instead of waiting until the file has been copied)
+            // Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display
+            task.Wait(2000);
         }
         }
 
 
         public void Post(OrganizeEpisode request)
         public void Post(OrganizeEpisode request)
@@ -168,6 +171,7 @@ namespace MediaBrowser.Api.Library
                 dicNewProviderIds = request.NewSeriesProviderIds;
                 dicNewProviderIds = request.NewSeriesProviderIds;
             }
             }
 
 
+            // Don't await this
             var task = _iFileOrganizationService.PerformEpisodeOrganization(new EpisodeFileOrganizationRequest
             var task = _iFileOrganizationService.PerformEpisodeOrganization(new EpisodeFileOrganizationRequest
             {
             {
                 EndingEpisodeNumber = request.EndingEpisodeNumber,
                 EndingEpisodeNumber = request.EndingEpisodeNumber,
@@ -182,11 +186,9 @@ namespace MediaBrowser.Api.Library
                 TargetFolder = request.TargetFolder
                 TargetFolder = request.TargetFolder
             });
             });
 
 
-            // For async processing (close dialog early instead of waiting until the file has been copied)
-            //var tasks = new Task[] { task };
-            //Task.WaitAll(tasks, 8000);
-
-            Task.WaitAll(task);
+            // Async processing (close dialog early instead of waiting until the file has been copied)
+            // Wait 2s for exceptions that may occur to have them forwarded to the client for immediate error display
+            task.Wait(2000);
         }
         }
 
 
         public object Get(GetSmartMatchInfos request)
         public object Get(GetSmartMatchInfos request)

+ 2 - 1
MediaBrowser.Api/Movies/MoviesService.cs

@@ -192,7 +192,8 @@ namespace MediaBrowser.Api.Movies
                 SortOrder = SortOrder.Descending,
                 SortOrder = SortOrder.Descending,
                 Limit = 7,
                 Limit = 7,
                 ParentId = parentIdGuid,
                 ParentId = parentIdGuid,
-                Recursive = true
+                Recursive = true,
+                IsPlayed = true
             };
             };
 
 
             var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();
             var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();

+ 99 - 51
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -298,7 +298,8 @@ namespace MediaBrowser.Api.Playback
             // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
             // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
             if (state.VideoType == VideoType.VideoFile)
             if (state.VideoType == VideoType.VideoFile)
             {
             {
-                var hwType = ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType;
+                var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
+                var hwType = encodingOptions.HardwareAccelerationType;
 
 
                 if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
                 if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
                     string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
                     string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
@@ -314,6 +315,10 @@ namespace MediaBrowser.Api.Playback
                 {
                 {
                     return GetAvailableEncoder("h264_omx", defaultEncoder);
                     return GetAvailableEncoder("h264_omx", defaultEncoder);
                 }
                 }
+                if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(encodingOptions.VaapiDevice))
+                {
+                    return GetAvailableEncoder("h264_vaapi", defaultEncoder);
+                }
             }
             }
 
 
             return defaultEncoder;
             return defaultEncoder;
@@ -427,7 +432,8 @@ namespace MediaBrowser.Api.Playback
 
 
             if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
             if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
             {
             {
-                if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
+                if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
+                    !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     // not supported by h264_omx
                     // not supported by h264_omx
                     param += " -profile:v " + state.VideoRequest.Profile;
                     param += " -profile:v " + state.VideoRequest.Profile;
@@ -482,7 +488,8 @@ namespace MediaBrowser.Api.Playback
 
 
             if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
             if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
                 !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
                 !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
-                !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+                !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
+                !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
             {
             {
                 param = "-pix_fmt yuv420p " + param;
                 param = "-pix_fmt yuv420p " + param;
             }
             }
@@ -548,59 +555,97 @@ namespace MediaBrowser.Api.Playback
 
 
             var filters = new List<string>();
             var filters = new List<string>();
 
 
-            if (state.DeInterlace)
+            if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+            {
+                filters.Add("format=nv12|vaapi");
+                filters.Add("hwupload");
+            }
+            else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
             {
             {
                 filters.Add("yadif=0:-1:0");
                 filters.Add("yadif=0:-1:0");
             }
             }
 
 
-            // If fixed dimensions were supplied
-            if (request.Width.HasValue && request.Height.HasValue)
+            if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
             {
             {
-                var widthParam = request.Width.Value.ToString(UsCulture);
-                var heightParam = request.Height.Value.ToString(UsCulture);
+                // Work around vaapi's reduced scaling features
+                var scaler = "scale_vaapi";
 
 
-                filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
-            }
+                // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions
+                // (outputWidth, outputHeight). The user may request precise output dimensions or maximum
+                // output dimensions. Output dimensions are guaranteed to be even.
+                decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width);
+                decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height);
+                decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth;
+                decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight;
+                decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth;
+                decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight;
 
 
-            // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
-            else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
-            {
-                var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
-                var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
+                if (outputWidth > maximumWidth || outputHeight > maximumHeight)
+                {
+                    var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight);
+                    outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale));
+                    outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale));
+                }
 
 
-                filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
-            }
+                outputWidth = 2 * Math.Truncate(outputWidth / 2);
+                outputHeight = 2 * Math.Truncate(outputHeight / 2);
 
 
-            // If a fixed width was requested
-            else if (request.Width.HasValue)
+                if (outputWidth != inputWidth || outputHeight != inputHeight)
+                {
+                    filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(UsCulture), outputHeight.ToString(UsCulture)));
+                }
+            }
+            else
             {
             {
-                var widthParam = request.Width.Value.ToString(UsCulture);
+                // If fixed dimensions were supplied
+                if (request.Width.HasValue && request.Height.HasValue)
+                {
+                    var widthParam = request.Width.Value.ToString(UsCulture);
+                    var heightParam = request.Height.Value.ToString(UsCulture);
 
 
-                filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
-            }
+                    filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
+                }
 
 
-            // If a fixed height was requested
-            else if (request.Height.HasValue)
-            {
-                var heightParam = request.Height.Value.ToString(UsCulture);
+                // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
+                else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
+                {
+                    var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
+                    var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
 
 
-                filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
-            }
+                    filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
+                }
 
 
-            // If a max width was requested
-            else if (request.MaxWidth.HasValue)
-            {
-                var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
+                // If a fixed width was requested
+                else if (request.Width.HasValue)
+                {
+                    var widthParam = request.Width.Value.ToString(UsCulture);
 
 
-                filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
-            }
+                    filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
+                }
 
 
-            // If a max height was requested
-            else if (request.MaxHeight.HasValue)
-            {
-                var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
+                // If a fixed height was requested
+                else if (request.Height.HasValue)
+                {
+                    var heightParam = request.Height.Value.ToString(UsCulture);
 
 
-                filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
+                    filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
+                }
+
+                // If a max width was requested
+                else if (request.MaxWidth.HasValue)
+                {
+                    var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
+
+                    filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
+                }
+
+                // If a max height was requested
+                else if (request.MaxHeight.HasValue)
+                {
+                    var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
+
+                    filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam));
+                }
             }
             }
 
 
             var output = string.Empty;
             var output = string.Empty;
@@ -935,6 +980,17 @@ namespace MediaBrowser.Api.Playback
                 }
                 }
             }
             }
 
 
+            if (state.VideoRequest != null)
+            {
+                var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
+                if (GetVideoEncoder(state).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
+                {
+                    arg = "-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
+                }
+            }
+
+            arg += string.Format(" -ss {0}", MediaEncoder.GetTimeParameter(TimeSpan.FromSeconds(1).Ticks)); 
+
             return arg.Trim();
             return arg.Trim();
         }
         }
 
 
@@ -1589,13 +1645,6 @@ namespace MediaBrowser.Api.Playback
                     }
                     }
                 }
                 }
                 else if (i == 25)
                 else if (i == 25)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.ForceLiveStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                    }
-                }
-                else if (i == 26)
                 {
                 {
                     if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
                     if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
                     {
                     {
@@ -1606,18 +1655,18 @@ namespace MediaBrowser.Api.Playback
                         }
                         }
                     }
                     }
                 }
                 }
-                else if (i == 27)
+                else if (i == 26)
                 {
                 {
                     request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
                     request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
                 }
                 }
-                else if (i == 28)
+                else if (i == 27)
                 {
                 {
                     if (videoRequest != null)
                     if (videoRequest != null)
                     {
                     {
                         videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
                         videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
                     }
                     }
                 }
                 }
-                else if (i == 29)
+                else if (i == 28)
                 {
                 {
                     request.Tag = val;
                     request.Tag = val;
                 }
                 }
@@ -2218,7 +2267,6 @@ namespace MediaBrowser.Api.Playback
                     if (state.VideoRequest != null)
                     if (state.VideoRequest != null)
                     {
                     {
                         state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
                         state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
-                        state.VideoRequest.ForceLiveStream = transcodingProfile.ForceLiveStream;
                         state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
                         state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
                     }
                     }
                 }
                 }
@@ -2244,7 +2292,7 @@ namespace MediaBrowser.Api.Playback
                 return Task.FromResult(true);
                 return Task.FromResult(true);
             }
             }
 
 
-            if (!string.Equals(MediaEncoder.EncoderLocationType, "Default", StringComparison.OrdinalIgnoreCase))
+            if (!MediaEncoder.IsDefaultEncoderPath)
             {
             {
                 return Task.FromResult(true);
                 return Task.FromResult(true);
             }
             }

+ 0 - 5
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -281,11 +281,6 @@ namespace MediaBrowser.Api.Playback.Hls
         {
         {
             var isLiveStream = (state.RunTimeTicks ?? 0) == 0;
             var isLiveStream = (state.RunTimeTicks ?? 0) == 0;
 
 
-            if (state.VideoRequest.ForceLiveStream)
-            {
-                return true;
-            }
-
             return isLiveStream;
             return isLiveStream;
         }
         }
     }
     }

+ 0 - 2
MediaBrowser.Api/Playback/StreamRequest.cs

@@ -193,8 +193,6 @@ namespace MediaBrowser.Api.Playback
         [ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool CopyTimestamps { get; set; }
         public bool CopyTimestamps { get; set; }
 
 
-        public bool ForceLiveStream { get; set; }
-
         public bool EnableSubtitlesInManifest { get; set; }
         public bool EnableSubtitlesInManifest { get; set; }
 
 
         public VideoStreamRequest()
         public VideoStreamRequest()

+ 0 - 2
MediaBrowser.Api/Reports/ReportsService.cs

@@ -275,8 +275,6 @@ namespace MediaBrowser.Api.Reports
                     case ItemFilter.IsPlayed:
                     case ItemFilter.IsPlayed:
                         query.IsPlayed = true;
                         query.IsPlayed = true;
                         break;
                         break;
-                    case ItemFilter.IsRecentlyAdded:
-                        break;
                     case ItemFilter.IsResumable:
                     case ItemFilter.IsResumable:
                         query.IsResumable = true;
                         query.IsResumable = true;
                         break;
                         break;

+ 13 - 0
MediaBrowser.Api/Sync/SyncHelper.cs

@@ -24,6 +24,19 @@ namespace MediaBrowser.Api.Sync
                         }
                         }
                         break;
                         break;
                     }
                     }
+                    if (item.IsAudio)
+                    {
+                        options.Add(SyncJobOption.Quality);
+                        options.Add(SyncJobOption.Profile);
+                        break;
+                    }
+                    if (item.IsMusicGenre || item.IsArtist|| item.IsType("musicalbum"))
+                    {
+                        options.Add(SyncJobOption.Quality);
+                        options.Add(SyncJobOption.Profile);
+                        options.Add(SyncJobOption.ItemLimit);
+                        break;
+                    }
                     if (item.IsFolderItem && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre)
                     if (item.IsFolderItem && !item.IsMusicGenre && !item.IsArtist && !item.IsType("musicalbum") && !item.IsGameGenre)
                     {
                     {
                         options.Add(SyncJobOption.Quality);
                         options.Add(SyncJobOption.Quality);

+ 2 - 1
MediaBrowser.Api/Sync/SyncService.cs

@@ -291,7 +291,8 @@ namespace MediaBrowser.Api.Sync
                 {
                 {
                     Fields = new List<ItemFields>
                     Fields = new List<ItemFields>
                     {
                     {
-                        ItemFields.SyncInfo
+                        ItemFields.SyncInfo,
+                        ItemFields.BasicSyncInfo
                     }
                     }
                 };
                 };
 
 

+ 0 - 2
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -164,8 +164,6 @@ namespace MediaBrowser.Api.UserLibrary
                     case ItemFilter.IsPlayed:
                     case ItemFilter.IsPlayed:
                         query.IsPlayed = true;
                         query.IsPlayed = true;
                         break;
                         break;
-                    case ItemFilter.IsRecentlyAdded:
-                        break;
                     case ItemFilter.IsResumable:
                     case ItemFilter.IsResumable:
                         query.IsResumable = true;
                         query.IsResumable = true;
                         break;
                         break;

+ 0 - 20
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -149,24 +149,6 @@ namespace MediaBrowser.Api.UserLibrary
                 item = user == null ? _libraryManager.RootFolder : user.RootFolder;
                 item = user == null ? _libraryManager.RootFolder : user.RootFolder;
             }
             }
 
 
-            if (!string.IsNullOrEmpty(request.Ids))
-            {
-                var query = GetItemsQuery(request, user);
-                var specificItems = _libraryManager.GetItemList(query).ToArray();
-                if (query.SortBy.Length == 0)
-                {
-                    var ids = query.ItemIds.ToList();
-
-                    // Try to preserve order
-                    specificItems = specificItems.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
-                }
-                return new QueryResult<BaseItem>
-                {
-                    Items = specificItems.ToArray(),
-                    TotalRecordCount = specificItems.Length
-                };
-            }
-
             // Default list type = children
             // Default list type = children
 
 
             var folder = item as Folder;
             var folder = item as Folder;
@@ -289,8 +271,6 @@ namespace MediaBrowser.Api.UserLibrary
                     case ItemFilter.IsPlayed:
                     case ItemFilter.IsPlayed:
                         query.IsPlayed = true;
                         query.IsPlayed = true;
                         break;
                         break;
-                    case ItemFilter.IsRecentlyAdded:
-                        break;
                     case ItemFilter.IsResumable:
                     case ItemFilter.IsResumable:
                         query.IsResumable = true;
                         query.IsResumable = true;
                         break;
                         break;

+ 0 - 1
MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs

@@ -33,7 +33,6 @@ namespace MediaBrowser.Common.Implementations.Updates
                 EnableKeepAlive = false,
                 EnableKeepAlive = false,
                 CancellationToken = cancellationToken,
                 CancellationToken = cancellationToken,
                 UserAgent = "Emby/3.0"
                 UserAgent = "Emby/3.0"
-
             };
             };
 
 
             if (_cacheLength.Ticks > 0)
             if (_cacheLength.Ticks > 0)

+ 18 - 4
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -281,6 +281,20 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
+        public Task UpdateIsOffline(bool newValue)
+        {
+            var item = this;
+
+            if (item.IsOffline != newValue)
+            {
+                item.IsOffline = newValue;
+                // this is creating too many repeated db updates
+                //return item.UpdateToRepository(ItemUpdateType.None, CancellationToken.None);
+            }
+
+            return Task.FromResult(true);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets or sets the type of the location.
         /// Gets or sets the type of the location.
         /// </summary>
         /// </summary>
@@ -290,10 +304,10 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             get
             get
             {
             {
-                if (IsOffline)
-                {
-                    return LocationType.Offline;
-                }
+                //if (IsOffline)
+                //{
+                //    return LocationType.Offline;
+                //}
 
 
                 if (string.IsNullOrWhiteSpace(Path))
                 if (string.IsNullOrWhiteSpace(Path))
                 {
                 {

+ 1 - 0
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -106,6 +106,7 @@ namespace MediaBrowser.Controller.Entities
             {
             {
                 LibraryOptions[path] = options;
                 LibraryOptions[path] = options;
 
 
+                options.SchemaVersion = 1;
                 XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path));
                 XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path));
             }
             }
         }
         }

+ 5 - 16
MediaBrowser.Controller/Entities/Folder.cs

@@ -375,7 +375,7 @@ namespace MediaBrowser.Controller.Entities
 
 
                     if (currentChildren.TryGetValue(child.Id, out currentChild) && IsValidFromResolver(currentChild, child))
                     if (currentChildren.TryGetValue(child.Id, out currentChild) && IsValidFromResolver(currentChild, child))
                     {
                     {
-                        await UpdateIsOffline(currentChild, false).ConfigureAwait(false);
+                        await currentChild.UpdateIsOffline(false).ConfigureAwait(false);
                         validChildren.Add(currentChild);
                         validChildren.Add(currentChild);
 
 
                         continue;
                         continue;
@@ -404,7 +404,7 @@ namespace MediaBrowser.Controller.Entities
 
 
                         else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
                         else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
                         {
                         {
-                            await UpdateIsOffline(item, true).ConfigureAwait(false);
+                            await item.UpdateIsOffline(true).ConfigureAwait(false);
                         }
                         }
                         else
                         else
                         {
                         {
@@ -461,17 +461,6 @@ namespace MediaBrowser.Controller.Entities
             progress.Report(100);
             progress.Report(100);
         }
         }
 
 
-        private Task UpdateIsOffline(BaseItem item, bool newValue)
-        {
-            if (item.IsOffline != newValue)
-            {
-                item.IsOffline = newValue;
-                return item.UpdateToRepository(ItemUpdateType.None, CancellationToken.None);
-            }
-
-            return Task.FromResult(true);
-        }
-
         private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
         private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
         {
         {
             var children = ActualChildren.ToList();
             var children = ActualChildren.ToList();
@@ -902,16 +891,16 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             if (query.ItemIds.Length > 0)
             if (query.ItemIds.Length > 0)
             {
             {
-                var specificItems = query.ItemIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
+                var result = LibraryManager.GetItemsResult(query);
 
 
                 if (query.SortBy.Length == 0)
                 if (query.SortBy.Length == 0)
                 {
                 {
                     var ids = query.ItemIds.ToList();
                     var ids = query.ItemIds.ToList();
 
 
                     // Try to preserve order
                     // Try to preserve order
-                    specificItems = specificItems.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToList();
+                    result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
                 }
                 }
-                return Task.FromResult(PostFilterAndSort(specificItems, query, true, true));
+                return Task.FromResult(result);
             }
             }
 
 
             return GetItemsInternal(query);
             return GetItemsInternal(query);

+ 25 - 21
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -135,7 +135,11 @@ namespace MediaBrowser.Controller.Entities.TV
         [IgnoreDataMember]
         [IgnoreDataMember]
         public Series Series
         public Series Series
         {
         {
-            get { return FindParent<Series>(); }
+            get
+            {
+                var seriesId = SeriesId ?? FindSeriesId();
+                return seriesId.HasValue ? (LibraryManager.GetItemById(seriesId.Value) as Series) : null;
+            }
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -143,24 +147,8 @@ namespace MediaBrowser.Controller.Entities.TV
         {
         {
             get
             get
             {
             {
-                var season = FindParent<Season>();
-
-                // Episodes directly in series folder
-                if (season == null)
-                {
-                    var series = Series;
-
-                    if (series != null && ParentIndexNumber.HasValue)
-                    {
-                        var findNumber = ParentIndexNumber.Value;
-
-                        season = series.Children
-                            .OfType<Season>()
-                            .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
-                    }
-                }
-
-                return season;
+                var seasonId = SeasonId ?? FindSeasonId();
+                return seasonId.HasValue ? (LibraryManager.GetItemById(seasonId.Value) as Season) : null;
             }
             }
         }
         }
 
 
@@ -193,7 +181,23 @@ namespace MediaBrowser.Controller.Entities.TV
 
 
         public Guid? FindSeasonId()
         public Guid? FindSeasonId()
         {
         {
-            var season = Season;
+            var season = FindParent<Season>();
+
+            // Episodes directly in series folder
+            if (season == null)
+            {
+                var series = Series;
+
+                if (series != null && ParentIndexNumber.HasValue)
+                {
+                    var findNumber = ParentIndexNumber.Value;
+
+                    season = series.Children
+                        .OfType<Season>()
+                        .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == findNumber);
+                }
+            }
+
             return season == null ? (Guid?)null : season.Id;
             return season == null ? (Guid?)null : season.Id;
         }
         }
 
 
@@ -263,7 +267,7 @@ namespace MediaBrowser.Controller.Entities.TV
 
 
         public Guid? FindSeriesId()
         public Guid? FindSeriesId()
         {
         {
-            var series = Series;
+            var series = FindParent<Series>();
             return series == null ? (Guid?)null : series.Id;
             return series == null ? (Guid?)null : series.Id;
         }
         }
 
 

+ 6 - 2
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -99,7 +99,11 @@ namespace MediaBrowser.Controller.Entities.TV
         [IgnoreDataMember]
         [IgnoreDataMember]
         public Series Series
         public Series Series
         {
         {
-            get { return FindParent<Series>(); }
+            get
+            {
+                var seriesId = SeriesId ?? FindSeriesId();
+                return seriesId.HasValue ? (LibraryManager.GetItemById(seriesId.Value) as Series) : null;
+            }
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -241,7 +245,7 @@ namespace MediaBrowser.Controller.Entities.TV
 
 
         public Guid? FindSeriesId()
         public Guid? FindSeriesId()
         {
         {
-            var series = Series;
+            var series = FindParent<Series>();
             return series == null ? (Guid?)null : series.Id;
             return series == null ? (Guid?)null : series.Id;
         }
         }
 
 

+ 23 - 1
MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs

@@ -1,5 +1,7 @@
-using MediaBrowser.Model.FileOrganization;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.FileOrganization;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
+using System;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
@@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.FileOrganization
 {
 {
     public interface IFileOrganizationService
     public interface IFileOrganizationService
     {
     {
+        event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemAdded;
+        event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemUpdated;
+        event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemRemoved;
+        event EventHandler LogReset;
+
         /// <summary>
         /// <summary>
         /// Processes the new files.
         /// Processes the new files.
         /// </summary>
         /// </summary>
@@ -81,5 +88,20 @@ namespace MediaBrowser.Controller.FileOrganization
         /// <param name="ItemName">Item name.</param>
         /// <param name="ItemName">Item name.</param>
         /// <param name="matchString">The match string to delete.</param>
         /// <param name="matchString">The match string to delete.</param>
         void DeleteSmartMatchEntry(string ItemName, string matchString);
         void DeleteSmartMatchEntry(string ItemName, string matchString);
+
+        /// <summary>
+        /// Attempts to add a an item to the list of currently processed items.
+        /// </summary>
+        /// <param name="result">The result item.</param>
+        /// <param name="fullClientRefresh">Passing true will notify the client to reload all items, otherwise only a single item will be refreshed.</param>
+        /// <returns>True if the item was added, False if the item is already contained in the list.</returns>
+        bool AddToInProgressList(FileOrganizationResult result, bool fullClientRefresh);
+
+        /// <summary>
+        /// Removes an item from the list of currently processed items.
+        /// </summary>
+        /// <param name="result">The result item.</param>
+        /// <returns>True if the item was removed, False if the item was not contained in the list.</returns>
+        bool RemoveFromInprogressList(FileOrganizationResult result);
     }
     }
 }
 }

+ 1 - 0
MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs

@@ -134,5 +134,6 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
         Task UpdateEncoderPath(string path, string pathType);
         Task UpdateEncoderPath(string path, string pathType);
         bool SupportsEncoder(string encoder);
         bool SupportsEncoder(string encoder);
+        bool IsDefaultEncoderPath { get; }
     }
     }
 }
 }

+ 6 - 1
MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs

@@ -36,8 +36,13 @@ namespace MediaBrowser.Controller.MediaEncoding
             return new[] {videoPath};
             return new[] {videoPath};
         }
         }
 
 
-        public static List<string> GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, IEnumerable<string> filenames)
+        private static List<string> GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, List<string> filenames)
         {
         {
+            if (filenames.Count == 0)
+            {
+                return new List<string>();
+            }
+
             var allFiles = fileSystem
             var allFiles = fileSystem
                 .GetFilePaths(rootPath, true)
                 .GetFilePaths(rootPath, true)
                 .ToList();
                 .ToList();

+ 9 - 0
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -724,6 +724,15 @@ namespace MediaBrowser.Controller.Providers
                         }
                         }
                         break;
                         break;
                     }
                     }
+                case "TvMazeId":
+                    {
+                        var id = reader.ReadElementContentAsString();
+                        if (!string.IsNullOrWhiteSpace(id))
+                        {
+                            item.SetProviderId(MetadataProviders.TvMaze, id);
+                        }
+                        break;
+                    }
                 case "AudioDbArtistId":
                 case "AudioDbArtistId":
                     {
                     {
                         var id = reader.ReadElementContentAsString();
                         var id = reader.ReadElementContentAsString();

+ 9 - 3
MediaBrowser.Controller/Session/ISessionManager.cs

@@ -172,15 +172,21 @@ namespace MediaBrowser.Controller.Session
         Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken);
         Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
-        /// Sends the message to user sessions.
+        /// Sends the message to admin sessions.
         /// </summary>
         /// </summary>
         /// <typeparam name="T"></typeparam>
         /// <typeparam name="T"></typeparam>
-        /// <param name="userId">The user identifier.</param>
         /// <param name="name">The name.</param>
         /// <param name="name">The name.</param>
         /// <param name="data">The data.</param>
         /// <param name="data">The data.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        Task SendMessageToUserSessions<T>(string userId, string name, T data, CancellationToken cancellationToken);
+        Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Sends the message to user sessions.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <returns>Task.</returns>
+        Task SendMessageToUserSessions<T>(List<string> userIds, string name, T data, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// Sends the message to user device sessions.
         /// Sends the message to user device sessions.

+ 5 - 5
MediaBrowser.Dlna/PlayTo/Device.cs

@@ -479,17 +479,17 @@ namespace MediaBrowser.Dlna.PlayTo
                 _successiveStopCount++;
                 _successiveStopCount++;
                 _connectFailureCount++;
                 _connectFailureCount++;
 
 
-                if (_successiveStopCount >= maxSuccessiveStopReturns)
-                {
-                    RestartTimerInactive();
-                }
-                if (_connectFailureCount >= maxSuccessiveStopReturns)
+                if (_connectFailureCount >= 3)
                 {
                 {
                     if (OnDeviceUnavailable != null)
                     if (OnDeviceUnavailable != null)
                     {
                     {
                         OnDeviceUnavailable();
                         OnDeviceUnavailable();
                     }
                     }
                 }
                 }
+                if (_successiveStopCount >= maxSuccessiveStopReturns)
+                {
+                    RestartTimerInactive();
+                }
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {

+ 1 - 1
MediaBrowser.Dlna/PlayTo/PlayToController.cs

@@ -99,11 +99,11 @@ namespace MediaBrowser.Dlna.PlayTo
         public void Init(Device device)
         public void Init(Device device)
         {
         {
             _device = device;
             _device = device;
+            _device.OnDeviceUnavailable = OnDeviceUnavailable;
             _device.PlaybackStart += _device_PlaybackStart;
             _device.PlaybackStart += _device_PlaybackStart;
             _device.PlaybackProgress += _device_PlaybackProgress;
             _device.PlaybackProgress += _device_PlaybackProgress;
             _device.PlaybackStopped += _device_PlaybackStopped;
             _device.PlaybackStopped += _device_PlaybackStopped;
             _device.MediaChanged += _device_MediaChanged;
             _device.MediaChanged += _device_MediaChanged;
-            _device.OnDeviceUnavailable = OnDeviceUnavailable;
 
 
             _device.Start();
             _device.Start();
 
 

+ 0 - 1
MediaBrowser.Dlna/Profiles/DefaultProfile.cs

@@ -33,7 +33,6 @@ namespace MediaBrowser.Dlna.Profiles
             MaxStreamingBitrate = 20000000;
             MaxStreamingBitrate = 20000000;
             MaxStaticBitrate = 20000000;
             MaxStaticBitrate = 20000000;
             MusicStreamingTranscodingBitrate = 192000;
             MusicStreamingTranscodingBitrate = 192000;
-            MusicSyncBitrate = 192000;
 
 
             EnableAlbumArtInDidl = false;
             EnableAlbumArtInDidl = false;
 
 

+ 0 - 2
MediaBrowser.Dlna/Profiles/KodiProfile.cs

@@ -11,9 +11,7 @@ namespace MediaBrowser.Dlna.Profiles
             Name = "Kodi";
             Name = "Kodi";
 
 
             MaxStreamingBitrate = 100000000;
             MaxStreamingBitrate = 100000000;
-            MaxStaticBitrate = 100000000;
             MusicStreamingTranscodingBitrate = 1280000;
             MusicStreamingTranscodingBitrate = 1280000;
-            MusicSyncBitrate = 1280000;
 
 
             TimelineOffsetSeconds = 5;
             TimelineOffsetSeconds = 5;
 
 

+ 7 - 0
MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs

@@ -553,6 +553,13 @@ namespace MediaBrowser.LocalMetadata.Savers
                 builder.Append("<TVRageId>" + SecurityElement.Escape(externalId) + "</TVRageId>");
                 builder.Append("<TVRageId>" + SecurityElement.Escape(externalId) + "</TVRageId>");
             }
             }
 
 
+            externalId = item.GetProviderId(MetadataProviders.TvMaze);
+
+            if (!string.IsNullOrEmpty(externalId))
+            {
+                builder.Append("<TvMazeId>" + SecurityElement.Escape(externalId) + "</TvMazeId>");
+            }
+
             var hasTagline = item as IHasTaglines;
             var hasTagline = item as IHasTaglines;
             if (hasTagline != null)
             if (hasTagline != null)
             {
             {

+ 75 - 43
MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs

@@ -680,7 +680,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             if (!string.IsNullOrEmpty(state.Options.Profile))
             if (!string.IsNullOrEmpty(state.Options.Profile))
             {
             {
-                if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
+                if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
+                    !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     // not supported by h264_omx
                     // not supported by h264_omx
                     param += " -profile:v " + state.Options.Profile;
                     param += " -profile:v " + state.Options.Profile;
@@ -737,7 +738,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
             if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
                 !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
                 !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
-                !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+                !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
+                !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
             {
             {
                 param = "-pix_fmt yuv420p " + param;
                 param = "-pix_fmt yuv420p " + param;
             }
             }
@@ -887,66 +889,96 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             var filters = new List<string>();
             var filters = new List<string>();
 
 
-            if (state.DeInterlace)
+            if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+            {
+                filters.Add("format=nv12|vaapi");
+                filters.Add("hwupload");
+            }
+            else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
             {
             {
                 filters.Add("yadif=0:-1:0");
                 filters.Add("yadif=0:-1:0");
             }
             }
 
 
-            // If fixed dimensions were supplied
-            if (request.Width.HasValue && request.Height.HasValue)
+            if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
             {
             {
-                var widthParam = request.Width.Value.ToString(UsCulture);
-                var heightParam = request.Height.Value.ToString(UsCulture);
+                // Work around vaapi's reduced scaling features
+                var scaler = "scale_vaapi";
 
 
-                filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
-            }
+                // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions
+                // (outputWidth, outputHeight). The user may request precise output dimensions or maximum
+                // output dimensions. Output dimensions are guaranteed to be even.
+                decimal inputWidth = Convert.ToDecimal(state.VideoStream.Width);
+                decimal inputHeight = Convert.ToDecimal(state.VideoStream.Height);
+                decimal outputWidth = request.Width.HasValue ? Convert.ToDecimal(request.Width.Value) : inputWidth;
+                decimal outputHeight = request.Height.HasValue ? Convert.ToDecimal(request.Height.Value) : inputHeight;
+                decimal maximumWidth = request.MaxWidth.HasValue ? Convert.ToDecimal(request.MaxWidth.Value) : outputWidth;
+                decimal maximumHeight = request.MaxHeight.HasValue ? Convert.ToDecimal(request.MaxHeight.Value) : outputHeight;
 
 
-            // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
-            else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
-            {
-                var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
-                var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
+                if (outputWidth > maximumWidth || outputHeight > maximumHeight)
+                {
+                    var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight);
+                    outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale));
+                    outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale));
+                }
 
 
-                filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
-            }
+                outputWidth = 2 * Math.Truncate(outputWidth / 2);
+                outputHeight = 2 * Math.Truncate(outputHeight / 2);
 
 
-            // If a fixed width was requested
-            else if (request.Width.HasValue)
+                if (outputWidth != inputWidth || outputHeight != inputHeight)
+                {
+                    filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(UsCulture), outputHeight.ToString(UsCulture)));
+                }
+            }
+            else
             {
             {
-                var widthParam = request.Width.Value.ToString(UsCulture);
+                // If fixed dimensions were supplied
+                if (request.Width.HasValue && request.Height.HasValue)
+                {
+                    var widthParam = request.Width.Value.ToString(UsCulture);
+                    var heightParam = request.Height.Value.ToString(UsCulture);
 
 
-                filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
-            }
+                    filters.Add(string.Format("scale=trunc({0}/2)*2:trunc({1}/2)*2", widthParam, heightParam));
+                }
 
 
-            // If a fixed height was requested
-            else if (request.Height.HasValue)
-            {
-                var heightParam = request.Height.Value.ToString(UsCulture);
+                // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
+                else if (request.MaxWidth.HasValue && request.MaxHeight.HasValue)
+                {
+                    var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
+                    var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
 
 
-                filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
-            }
+                    filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam));
+                }
 
 
-            // If a max width was requested
-            else if (request.MaxWidth.HasValue)
-            {
-                var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
+                // If a fixed width was requested
+                else if (request.Width.HasValue)
+                {
+                    var widthParam = request.Width.Value.ToString(UsCulture);
 
 
-                filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
-            }
+                    filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam));
+                }
 
 
-            // If a max height was requested
-            else if (request.MaxHeight.HasValue)
-            {
-                var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
+                // If a fixed height was requested
+                else if (request.Height.HasValue)
+                {
+                    var heightParam = request.Height.Value.ToString(UsCulture);
 
 
-                filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
-            }
+                    filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam));
+                }
 
 
-            if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
-            {
-                if (filters.Count > 1)
+                // If a max width was requested
+                else if (request.MaxWidth.HasValue)
+                {
+                    var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
+
+                    filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
+                }
+
+                // If a max height was requested
+                else if (request.MaxHeight.HasValue)
                 {
                 {
-                    //filters[filters.Count - 1] += ":flags=fast_bilinear";
+                    var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
+
+                    filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam));
                 }
                 }
             }
             }
 
 

+ 4 - 0
MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs

@@ -586,6 +586,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 {
                 {
                     return GetAvailableEncoder(mediaEncoder, "h264_omx", defaultEncoder);
                     return GetAvailableEncoder(mediaEncoder, "h264_omx", defaultEncoder);
                 }
                 }
+                if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(options.VaapiDevice))
+                {
+                    return GetAvailableEncoder(mediaEncoder, "h264_vaapi", defaultEncoder);
+                }
             }
             }
 
 
             return defaultEncoder;
             return defaultEncoder;

+ 8 - 8
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -123,20 +123,20 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     return "System";
                     return "System";
                 }
                 }
 
 
-                if (IsDefaultPath(FFMpegPath))
-                {
-                    return "Default";
-                }
-
                 return "Custom";
                 return "Custom";
             }
             }
         }
         }
 
 
-        private bool IsDefaultPath(string path)
+        public bool IsDefaultEncoderPath
         {
         {
-            var parentPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg", "20160410");
+            get
+            {
+                var path = FFMpegPath;
+
+                var parentPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg", "20160410");
 
 
-            return FileSystem.ContainsSubPath(parentPath, path);
+                return FileSystem.ContainsSubPath(parentPath, path);
+            }
         }
         }
 
 
         private bool IsSystemInstalledPath(string path)
         private bool IsSystemInstalledPath(string path)

+ 2 - 0
MediaBrowser.Model/Configuration/EncodingOptions.cs

@@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Configuration
         public int ThrottleDelaySeconds { get; set; }
         public int ThrottleDelaySeconds { get; set; }
         public string HardwareAccelerationType { get; set; }
         public string HardwareAccelerationType { get; set; }
         public string EncoderAppPath { get; set; }
         public string EncoderAppPath { get; set; }
+        public string VaapiDevice { get; set; }
 
 
         public EncodingOptions()
         public EncodingOptions()
         {
         {
@@ -17,6 +18,7 @@ namespace MediaBrowser.Model.Configuration
             EnableThrottling = true;
             EnableThrottling = true;
             ThrottleDelaySeconds = 180;
             ThrottleDelaySeconds = 180;
             EncodingThreadCount = -1;
             EncodingThreadCount = -1;
+            VaapiDevice = "/dev/dri/card0";
         }
         }
     }
     }
 }
 }

+ 3 - 0
MediaBrowser.Model/Configuration/LibraryOptions.cs

@@ -4,10 +4,13 @@
     {
     {
         public bool EnableArchiveMediaFiles { get; set; }
         public bool EnableArchiveMediaFiles { get; set; }
         public bool EnablePhotos { get; set; }
         public bool EnablePhotos { get; set; }
+        public bool EnableRealtimeMonitor { get; set; }
+        public int SchemaVersion { get; set; }
 
 
         public LibraryOptions()
         public LibraryOptions()
         {
         {
             EnablePhotos = true;
             EnablePhotos = true;
+            EnableRealtimeMonitor = true;
         }
         }
     }
     }
 }
 }

+ 2 - 0
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -74,6 +74,8 @@ namespace MediaBrowser.Model.Configuration
         /// <value>The metadata path.</value>
         /// <value>The metadata path.</value>
         public string MetadataPath { get; set; }
         public string MetadataPath { get; set; }
 
 
+        public string LastVersion { get; set; }
+
         /// <summary>
         /// <summary>
         /// Gets or sets the display name of the season zero.
         /// Gets or sets the display name of the season zero.
         /// </summary>
         /// </summary>

+ 0 - 3
MediaBrowser.Model/Configuration/UserConfiguration.cs

@@ -27,8 +27,6 @@ namespace MediaBrowser.Model.Configuration
         public bool DisplayMissingEpisodes { get; set; }
         public bool DisplayMissingEpisodes { get; set; }
         public bool DisplayUnairedEpisodes { get; set; }
         public bool DisplayUnairedEpisodes { get; set; }
 
 
-        public bool GroupMoviesIntoBoxSets { get; set; }
-
         public string[] ExcludeFoldersFromGrouping { get; set; }
         public string[] ExcludeFoldersFromGrouping { get; set; }
         public string[] GroupedFolders { get; set; }
         public string[] GroupedFolders { get; set; }
 
 
@@ -48,7 +46,6 @@ namespace MediaBrowser.Model.Configuration
         public bool RememberAudioSelections { get; set; }
         public bool RememberAudioSelections { get; set; }
         public bool RememberSubtitleSelections { get; set; }
         public bool RememberSubtitleSelections { get; set; }
         public bool EnableNextEpisodeAutoPlay { get; set; }
         public bool EnableNextEpisodeAutoPlay { get; set; }
-        public bool DisplayFoldersView { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="UserConfiguration" /> class.
         /// Initializes a new instance of the <see cref="UserConfiguration" /> class.

+ 5 - 1
MediaBrowser.Model/Dlna/AudioOptions.cs

@@ -59,7 +59,7 @@ namespace MediaBrowser.Model.Dlna
         /// Gets the maximum bitrate.
         /// Gets the maximum bitrate.
         /// </summary>
         /// </summary>
         /// <returns>System.Nullable&lt;System.Int32&gt;.</returns>
         /// <returns>System.Nullable&lt;System.Int32&gt;.</returns>
-        public int? GetMaxBitrate()
+        public int? GetMaxBitrate(bool isAudio)
         {
         {
             if (MaxBitrate.HasValue)
             if (MaxBitrate.HasValue)
             {
             {
@@ -70,6 +70,10 @@ namespace MediaBrowser.Model.Dlna
             {
             {
                 if (Context == EncodingContext.Static)
                 if (Context == EncodingContext.Static)
                 {
                 {
+                    if (isAudio && Profile.MaxStaticMusicBitrate.HasValue)
+                    {
+                        return Profile.MaxStaticMusicBitrate;
+                    }
                     return Profile.MaxStaticBitrate;
                     return Profile.MaxStaticBitrate;
                 }
                 }
 
 

+ 1 - 2
MediaBrowser.Model/Dlna/DeviceProfile.cs

@@ -55,7 +55,7 @@ namespace MediaBrowser.Model.Dlna
         public int? MaxStaticBitrate { get; set; }
         public int? MaxStaticBitrate { get; set; }
 
 
         public int? MusicStreamingTranscodingBitrate { get; set; }
         public int? MusicStreamingTranscodingBitrate { get; set; }
-        public int? MusicSyncBitrate { get; set; }
+        public int? MaxStaticMusicBitrate { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Controls the content of the X_DLNADOC element in the urn:schemas-dlna-org:device-1-0 namespace.
         /// Controls the content of the X_DLNADOC element in the urn:schemas-dlna-org:device-1-0 namespace.
@@ -115,7 +115,6 @@ namespace MediaBrowser.Model.Dlna
             MaxStreamingBitrate = 8000000;
             MaxStreamingBitrate = 8000000;
             MaxStaticBitrate = 8000000;
             MaxStaticBitrate = 8000000;
             MusicStreamingTranscodingBitrate = 128000;
             MusicStreamingTranscodingBitrate = 128000;
-            MusicSyncBitrate = 128000;
         }
         }
 
 
         public List<string> GetSupportedMediaTypes()
         public List<string> GetSupportedMediaTypes()

+ 26 - 22
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -55,7 +55,7 @@ namespace MediaBrowser.Model.Dlna
                 stream.DeviceProfileId = options.Profile.Id;
                 stream.DeviceProfileId = options.Profile.Id;
             }
             }
 
 
-            return GetOptimalStream(streams, options.GetMaxBitrate());
+            return GetOptimalStream(streams, options.GetMaxBitrate(true));
         }
         }
 
 
         public StreamInfo BuildVideoItem(VideoOptions options)
         public StreamInfo BuildVideoItem(VideoOptions options)
@@ -88,7 +88,7 @@ namespace MediaBrowser.Model.Dlna
                 stream.DeviceProfileId = options.Profile.Id;
                 stream.DeviceProfileId = options.Profile.Id;
             }
             }
 
 
-            return GetOptimalStream(streams, options.GetMaxBitrate());
+            return GetOptimalStream(streams, options.GetMaxBitrate(false));
         }
         }
 
 
         private StreamInfo GetOptimalStream(List<StreamInfo> streams, int? maxBitrate)
         private StreamInfo GetOptimalStream(List<StreamInfo> streams, int? maxBitrate)
@@ -275,24 +275,32 @@ namespace MediaBrowser.Model.Dlna
                     playlistItem.MaxAudioChannels = Math.Min(options.MaxAudioChannels.Value, currentValue);
                     playlistItem.MaxAudioChannels = Math.Min(options.MaxAudioChannels.Value, currentValue);
                 }
                 }
 
 
-                int configuredBitrate = options.AudioTranscodingBitrate ??
-                    (options.Context == EncodingContext.Static ? options.Profile.MusicSyncBitrate : options.Profile.MusicStreamingTranscodingBitrate) ??
+                int transcodingBitrate = options.AudioTranscodingBitrate ??
+                    options.Profile.MusicStreamingTranscodingBitrate ??
                     128000;
                     128000;
 
 
-                playlistItem.AudioBitrate = Math.Min(configuredBitrate, playlistItem.AudioBitrate ?? configuredBitrate);
+                int? configuredBitrate = options.GetMaxBitrate(true);
+
+                if (configuredBitrate.HasValue)
+                {
+                    transcodingBitrate = Math.Min(configuredBitrate.Value, transcodingBitrate);
+                }
+
+                playlistItem.AudioBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate);
+
             }
             }
 
 
             return playlistItem;
             return playlistItem;
         }
         }
 
 
-        private int? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options)
+        private int? GetBitrateForDirectPlayCheck(MediaSourceInfo item, AudioOptions options, bool isAudio)
         {
         {
             if (item.Protocol == MediaProtocol.File)
             if (item.Protocol == MediaProtocol.File)
             {
             {
                 return options.Profile.MaxStaticBitrate;
                 return options.Profile.MaxStaticBitrate;
             }
             }
 
 
-            return options.GetMaxBitrate();
+            return options.GetMaxBitrate(isAudio);
         }
         }
 
 
         private List<PlayMethod> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
         private List<PlayMethod> GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
@@ -312,7 +320,7 @@ namespace MediaBrowser.Model.Dlna
             if (directPlayProfile != null)
             if (directPlayProfile != null)
             {
             {
                 // While options takes the network and other factors into account. Only applies to direct stream
                 // While options takes the network and other factors into account. Only applies to direct stream
-                if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()) && options.EnableDirectStream)
+                if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true)) && options.EnableDirectStream)
                 {
                 {
                     playMethods.Add(PlayMethod.DirectStream);
                     playMethods.Add(PlayMethod.DirectStream);
                 }
                 }
@@ -320,7 +328,7 @@ namespace MediaBrowser.Model.Dlna
                 // The profile describes what the device supports
                 // The profile describes what the device supports
                 // If device requirements are satisfied then allow both direct stream and direct play
                 // If device requirements are satisfied then allow both direct stream and direct play
                 if (item.SupportsDirectPlay &&
                 if (item.SupportsDirectPlay &&
-                    IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)) && options.EnableDirectPlay)
+                    IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true)) && options.EnableDirectPlay)
                 {
                 {
                     playMethods.Add(PlayMethod.DirectPlay);
                     playMethods.Add(PlayMethod.DirectPlay);
                 }
                 }
@@ -403,8 +411,8 @@ namespace MediaBrowser.Model.Dlna
             MediaStream videoStream = item.VideoStream;
             MediaStream videoStream = item.VideoStream;
 
 
             // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
             // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
-            bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay));
-            bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream));
+            bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true), subtitleStream, options, PlayMethod.DirectPlay));
+            bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || IsEligibleForDirectPlay(item, options.GetMaxBitrate(false), subtitleStream, options, PlayMethod.DirectStream));
 
 
             _logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
             _logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
                 options.Profile.Name ?? "Unknown Profile",
                 options.Profile.Name ?? "Unknown Profile",
@@ -469,7 +477,6 @@ namespace MediaBrowser.Model.Dlna
 
 
                 playlistItem.VideoCodec = transcodingProfile.VideoCodec;
                 playlistItem.VideoCodec = transcodingProfile.VideoCodec;
                 playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
                 playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
-                playlistItem.ForceLiveStream = transcodingProfile.ForceLiveStream;
                 playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
                 playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
 
 
                 if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels))
                 if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels))
@@ -570,10 +577,10 @@ namespace MediaBrowser.Model.Dlna
                     playlistItem.MaxAudioChannels = Math.Min(options.MaxAudioChannels.Value, currentValue);
                     playlistItem.MaxAudioChannels = Math.Min(options.MaxAudioChannels.Value, currentValue);
                 }
                 }
 
 
-                int audioBitrate = GetAudioBitrate(playlistItem.SubProtocol, options.GetMaxBitrate(), playlistItem.TargetAudioChannels, playlistItem.TargetAudioCodec, audioStream);
+                int audioBitrate = GetAudioBitrate(playlistItem.SubProtocol, options.GetMaxBitrate(false), playlistItem.TargetAudioChannels, playlistItem.TargetAudioCodec, audioStream);
                 playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate);
                 playlistItem.AudioBitrate = Math.Min(playlistItem.AudioBitrate ?? audioBitrate, audioBitrate);
 
 
-                int? maxBitrateSetting = options.GetMaxBitrate();
+                int? maxBitrateSetting = options.GetMaxBitrate(false);
                 // Honor max rate
                 // Honor max rate
                 if (maxBitrateSetting.HasValue)
                 if (maxBitrateSetting.HasValue)
                 {
                 {
@@ -595,19 +602,16 @@ namespace MediaBrowser.Model.Dlna
 
 
         private int GetAudioBitrate(string subProtocol, int? maxTotalBitrate, int? targetAudioChannels, string targetAudioCodec, MediaStream audioStream)
         private int GetAudioBitrate(string subProtocol, int? maxTotalBitrate, int? targetAudioChannels, string targetAudioCodec, MediaStream audioStream)
         {
         {
-            var defaultBitrate = 128000;
-            if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3"))
-            {
-                defaultBitrate = 192000;
-            }
-            if (!string.IsNullOrEmpty(targetAudioCodec) && audioStream != null && StringHelper.EqualsIgnoreCase(audioStream.Codec, targetAudioCodec))
+            var defaultBitrate = audioStream.BitRate ?? 192000;
+            // Reduce the bitrate if we're downmixing
+            if (targetAudioChannels.HasValue && audioStream != null && audioStream.Channels.HasValue && targetAudioChannels.Value < audioStream.Channels.Value)
             {
             {
-                defaultBitrate = audioStream.BitRate ?? defaultBitrate;
+                defaultBitrate = StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3") ? 192000 : 128000;
             }
             }
 
 
             if (targetAudioChannels.HasValue)
             if (targetAudioChannels.HasValue)
             {
             {
-                if (targetAudioChannels.Value >= 5 && (maxTotalBitrate ?? 0) >= 1500000)
+                if (targetAudioChannels.Value >= 5 && (maxTotalBitrate ?? 0) >= 1200000)
                 {
                 {
                     if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3"))
                     if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3"))
                     {
                     {

+ 1 - 3
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -36,7 +36,6 @@ namespace MediaBrowser.Model.Dlna
         public string VideoProfile { get; set; }
         public string VideoProfile { get; set; }
 
 
         public bool CopyTimestamps { get; set; }
         public bool CopyTimestamps { get; set; }
-        public bool ForceLiveStream { get; set; }
         public bool EnableSubtitlesInManifest { get; set; }
         public bool EnableSubtitlesInManifest { get; set; }
         public string[] AudioCodecs { get; set; }
         public string[] AudioCodecs { get; set; }
 
 
@@ -216,7 +215,7 @@ namespace MediaBrowser.Model.Dlna
             list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty));
             list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty));
             list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty));
             list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty));
 
 
-            if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls") && !item.ForceLiveStream)
+            if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls"))
             {
             {
                 list.Add(new NameValuePair("StartTimeTicks", string.Empty));
                 list.Add(new NameValuePair("StartTimeTicks", string.Empty));
             }
             }
@@ -246,7 +245,6 @@ namespace MediaBrowser.Model.Dlna
             }
             }
 
 
             list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString().ToLower()));
             list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString().ToLower()));
-            list.Add(new NameValuePair("ForceLiveStream", item.ForceLiveStream.ToString().ToLower()));
             list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
             list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
 
 
             list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.TranscodingMaxAudioChannels.Value) : string.Empty));
             list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.TranscodingMaxAudioChannels.Value) : string.Empty));

+ 0 - 3
MediaBrowser.Model/Dlna/TranscodingProfile.cs

@@ -35,9 +35,6 @@ namespace MediaBrowser.Model.Dlna
         [XmlAttribute("context")]
         [XmlAttribute("context")]
         public EncodingContext Context { get; set; }
         public EncodingContext Context { get; set; }
 
 
-        [XmlAttribute("forceLiveStream")]
-        public bool ForceLiveStream { get; set; }
-
         [XmlAttribute("enableSubtitlesInManifest")]
         [XmlAttribute("enableSubtitlesInManifest")]
         public bool EnableSubtitlesInManifest { get; set; }
         public bool EnableSubtitlesInManifest { get; set; }
 
 

+ 2 - 1
MediaBrowser.Model/Entities/MetadataProviders.cs

@@ -39,6 +39,7 @@ namespace MediaBrowser.Model.Entities
         TvRage = 15,
         TvRage = 15,
         AudioDbArtist = 16,
         AudioDbArtist = 16,
         AudioDbAlbum = 17,
         AudioDbAlbum = 17,
-        MusicBrainzTrack = 18
+        MusicBrainzTrack = 18,
+        TvMaze = 19
     }
     }
 }
 }

+ 6 - 0
MediaBrowser.Model/FileOrganization/FileOrganizationResult.cs

@@ -95,6 +95,12 @@ namespace MediaBrowser.Model.FileOrganization
         /// <value>The size of the file.</value>
         /// <value>The size of the file.</value>
         public long FileSize { get; set; }
         public long FileSize { get; set; }
 
 
+        /// <summary>
+        /// Indicates if the item is currently being processed.
+        /// </summary>
+        /// <remarks>Runtime property not persisted to the store.</remarks>
+        public bool IsInProgress { get; set; }
+
         public FileOrganizationResult()
         public FileOrganizationResult()
         {
         {
             DuplicatePaths = new List<string>();
             DuplicatePaths = new List<string>();

+ 1 - 1
MediaBrowser.Model/Net/MimeTypes.cs

@@ -241,7 +241,7 @@ namespace MediaBrowser.Model.Net
 			}
 			}
 			if (StringHelper.EqualsIgnoreCase(ext, ".opus"))
 			if (StringHelper.EqualsIgnoreCase(ext, ".opus"))
 			{
 			{
-				return "audio/opus";
+				return "audio/ogg";
 			}
 			}
 
 
             // Playlists
             // Playlists

+ 0 - 4
MediaBrowser.Model/Querying/ItemFilter.cs

@@ -27,10 +27,6 @@ namespace MediaBrowser.Model.Querying
         /// </summary>
         /// </summary>
         IsFavorite = 5,
         IsFavorite = 5,
         /// <summary>
         /// <summary>
-        /// The is recently added
-        /// </summary>
-        IsRecentlyAdded = 6,
-        /// <summary>
         /// The item is resumable
         /// The item is resumable
         /// </summary>
         /// </summary>
         IsResumable = 7,
         IsResumable = 7,

+ 6 - 3
MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs

@@ -167,10 +167,13 @@ namespace MediaBrowser.Providers.MediaInfo
 
 
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
         {
         {
-            var file = directoryService.GetFile(item.Path);
-            if (file != null && file.LastWriteTimeUtc != item.DateModified)
+            if (item.EnableRefreshOnDateModifiedChange && !string.IsNullOrWhiteSpace(item.Path) && item.LocationType == LocationType.FileSystem)
             {
             {
-                return true;
+                var file = directoryService.GetFile(item.Path);
+                if (file != null && file.LastWriteTimeUtc != item.DateModified)
+                {
+                    return true;
+                }
             }
             }
 
 
             return false;
             return false;

+ 1 - 1
MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs

@@ -171,7 +171,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
 
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
         {
         {
-            if (item.EnableRefreshOnDateModifiedChange && !string.IsNullOrWhiteSpace(item.Path))
+            if (item.EnableRefreshOnDateModifiedChange && !string.IsNullOrWhiteSpace(item.Path) && item.LocationType == LocationType.FileSystem)
             {
             {
                 var file = directoryService.GetFile(item.Path);
                 var file = directoryService.GetFile(item.Path);
                 if (file != null && file.LastWriteTimeUtc != item.DateModified)
                 if (file != null && file.LastWriteTimeUtc != item.DateModified)

+ 1 - 1
MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs

@@ -194,7 +194,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
 
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
         {
         {
-            if (item.EnableRefreshOnDateModifiedChange)
+            if (item.EnableRefreshOnDateModifiedChange && !string.IsNullOrWhiteSpace(item.Path) && item.LocationType == LocationType.FileSystem)
             {
             {
                 var file = directoryService.GetFile(item.Path);
                 var file = directoryService.GetFile(item.Path);
                 if (file != null && file.LastWriteTimeUtc != item.DateModified)
                 if (file != null && file.LastWriteTimeUtc != item.DateModified)

+ 6 - 3
MediaBrowser.Providers/Photos/PhotoProvider.cs

@@ -154,10 +154,13 @@ namespace MediaBrowser.Providers.Photos
 
 
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
         public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
         {
         {
-            var file = directoryService.GetFile(item.Path);
-            if (file != null && file.LastWriteTimeUtc != item.DateModified)
+            if (item.EnableRefreshOnDateModifiedChange && !string.IsNullOrWhiteSpace(item.Path) && item.LocationType == LocationType.FileSystem)
             {
             {
-                return true;
+                var file = directoryService.GetFile(item.Path);
+                if (file != null && file.LastWriteTimeUtc != item.DateModified)
+                {
+                    return true;
+                }
             }
             }
 
 
             return false;
             return false;

+ 7 - 10
MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs

@@ -54,18 +54,15 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
 
         private async void SendMessage(string name, TimerEventInfo info)
         private async void SendMessage(string name, TimerEventInfo info)
         {
         {
-            var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).ToList();
+            var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id.ToString("N")).ToList();
 
 
-            foreach (var user in users)
+            try
             {
             {
-                try
-                {
-                    await _sessionManager.SendMessageToUserSessions<TimerEventInfo>(user.Id.ToString("N"), name, info, CancellationToken.None);
-                }
-                catch (Exception ex)
-                {
-                    _logger.ErrorException("Error sending message", ex);
-                }
+                await _sessionManager.SendMessageToUserSessions<TimerEventInfo>(users, name, info, CancellationToken.None);
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error sending message", ex);
             }
             }
         }
         }
 
 

+ 2 - 1
MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs

@@ -11,6 +11,7 @@ using MediaBrowser.Controller.Sync;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Sync;
 using MediaBrowser.Model.Sync;
 using System;
 using System;
+using System.Collections.Generic;
 using System.Threading;
 using System.Threading;
 
 
 namespace MediaBrowser.Server.Implementations.EntryPoints
 namespace MediaBrowser.Server.Implementations.EntryPoints
@@ -164,7 +165,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
 
         private async void SendMessageToUserSession<T>(User user, string name, T data)
         private async void SendMessageToUserSession<T>(User user, string name, T data)
         {
         {
-            await _sessionManager.SendMessageToUserSessions(user.Id.ToString("N"), name, data, CancellationToken.None);
+            await _sessionManager.SendMessageToUserSessions(new List<string> { user.Id.ToString("N") }, name, data, CancellationToken.None);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 16 - 0
MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs

@@ -272,6 +272,18 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
 
             var originalExtractedSeriesString = result.ExtractedName;
             var originalExtractedSeriesString = result.ExtractedName;
 
 
+            bool isNew = string.IsNullOrWhiteSpace(result.Id);
+
+            if (isNew)
+            {
+                await _organizationService.SaveResult(result, cancellationToken);
+            }
+
+            if (!_organizationService.AddToInProgressList(result, isNew))
+            {
+                throw new Exception("File is currently processed otherwise. Please try again later.");
+            }
+            
             try
             try
             {
             {
             // Proceed to sort the file
             // Proceed to sort the file
@@ -363,6 +375,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 _logger.Warn(ex.Message);
                 _logger.Warn(ex.Message);
                 return;
                 return;
             }
             }
+            finally
+            {
+                _organizationService.RemoveFromInprogressList(result);
+            }
 
 
             if (rememberCorrection)
             if (rememberCorrection)
             {
             {

+ 68 - 0
MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationNotifier.cs

@@ -0,0 +1,68 @@
+using MediaBrowser.Controller.FileOrganization;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.FileOrganization;
+using MediaBrowser.Controller.Session;
+using System.Threading;
+
+namespace MediaBrowser.Server.Implementations.FileOrganization
+{
+    /// <summary>
+    /// Class SessionInfoWebSocketListener
+    /// </summary>
+    class FileOrganizationNotifier : IServerEntryPoint
+    {
+        private readonly IFileOrganizationService _organizationService;
+        private readonly ISessionManager _sessionManager;
+
+        public FileOrganizationNotifier(ILogger logger, IFileOrganizationService organizationService, ISessionManager sessionManager)
+        {
+            _organizationService = organizationService;
+            _sessionManager = sessionManager;
+        }
+
+        public void Run()
+        {
+            _organizationService.ItemAdded += _organizationService_ItemAdded;
+            _organizationService.ItemRemoved += _organizationService_ItemRemoved;
+            _organizationService.ItemUpdated += _organizationService_ItemUpdated;
+            _organizationService.LogReset += _organizationService_LogReset;
+        }
+
+        private void _organizationService_LogReset(object sender, EventArgs e)
+        {
+            _sessionManager.SendMessageToAdminSessions("AutoOrganizeUpdate", (FileOrganizationResult)null, CancellationToken.None);
+        }
+
+        private void _organizationService_ItemUpdated(object sender, GenericEventArgs<FileOrganizationResult> e)
+        {
+            _sessionManager.SendMessageToAdminSessions("AutoOrganizeUpdate", e.Argument, CancellationToken.None);
+        }
+
+        private void _organizationService_ItemRemoved(object sender, GenericEventArgs<FileOrganizationResult> e)
+        {
+            _sessionManager.SendMessageToAdminSessions("AutoOrganizeUpdate", (FileOrganizationResult)null, CancellationToken.None);
+        }
+
+        private void _organizationService_ItemAdded(object sender, GenericEventArgs<FileOrganizationResult> e)
+        {
+            _sessionManager.SendMessageToAdminSessions("AutoOrganizeUpdate", (FileOrganizationResult)null, CancellationToken.None);
+        }
+
+        public void Dispose()
+        {
+            _organizationService.ItemAdded -= _organizationService_ItemAdded;
+            _organizationService.ItemRemoved -= _organizationService_ItemRemoved;
+            _organizationService.ItemUpdated -= _organizationService_ItemUpdated;
+            _organizationService.LogReset -= _organizationService_LogReset;
+        }
+
+
+    }
+}

+ 94 - 6
MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs

@@ -3,16 +3,21 @@ using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.FileOrganization;
 using MediaBrowser.Controller.FileOrganization;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.FileOrganization;
 using MediaBrowser.Model.FileOrganization;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
 using System;
 using System;
+using System.Collections.Concurrent;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Common.Events;
 
 
 namespace MediaBrowser.Server.Implementations.FileOrganization
 namespace MediaBrowser.Server.Implementations.FileOrganization
 {
 {
@@ -26,6 +31,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
         private readonly IProviderManager _providerManager;
         private readonly IProviderManager _providerManager;
+        private readonly ConcurrentDictionary<string, bool> _inProgressItemIds = new ConcurrentDictionary<string, bool>();
+
+        public event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemAdded;
+        public event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemUpdated;
+        public event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemRemoved;
+        public event EventHandler LogReset;
 
 
         public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager)
         public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager)
         {
         {
@@ -58,12 +69,26 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
 
 
         public QueryResult<FileOrganizationResult> GetResults(FileOrganizationResultQuery query)
         public QueryResult<FileOrganizationResult> GetResults(FileOrganizationResultQuery query)
         {
         {
-            return _repo.GetResults(query);
+            var results = _repo.GetResults(query);
+
+            foreach (var result in results.Items)
+            {
+                result.IsInProgress = _inProgressItemIds.ContainsKey(result.Id);
+            }
+
+            return results;
         }
         }
 
 
         public FileOrganizationResult GetResult(string id)
         public FileOrganizationResult GetResult(string id)
         {
         {
-            return _repo.GetResult(id);
+            var result = _repo.GetResult(id);
+
+            if (result != null)
+            {
+                result.IsInProgress = _inProgressItemIds.ContainsKey(result.Id);
+            }
+
+            return result;
         }
         }
 
 
         public FileOrganizationResult GetResultBySourcePath(string path)
         public FileOrganizationResult GetResultBySourcePath(string path)
@@ -78,11 +103,17 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             return GetResult(id);
             return GetResult(id);
         }
         }
 
 
-        public Task DeleteOriginalFile(string resultId)
+        public async Task DeleteOriginalFile(string resultId)
         {
         {
             var result = _repo.GetResult(resultId);
             var result = _repo.GetResult(resultId);
 
 
             _logger.Info("Requested to delete {0}", result.OriginalPath);
             _logger.Info("Requested to delete {0}", result.OriginalPath);
+
+            if (!AddToInProgressList(result, false))
+            {
+                throw new Exception("Path is currently processed otherwise. Please try again later.");
+            }
+
             try
             try
             {
             {
                 _fileSystem.DeleteFile(result.OriginalPath);
                 _fileSystem.DeleteFile(result.OriginalPath);
@@ -91,8 +122,14 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             {
             {
                 _logger.ErrorException("Error deleting {0}", ex, result.OriginalPath);
                 _logger.ErrorException("Error deleting {0}", ex, result.OriginalPath);
             }
             }
+            finally
+            {
+                RemoveFromInprogressList(result);
+            }
 
 
-            return _repo.Delete(resultId);
+            await _repo.Delete(resultId);
+
+            EventHelper.FireEventIfNotNull(ItemRemoved, this, new GenericEventArgs<FileOrganizationResult>(result), _logger);
         }
         }
 
 
         private AutoOrganizeOptions GetAutoOrganizeOptions()
         private AutoOrganizeOptions GetAutoOrganizeOptions()
@@ -121,9 +158,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
             }
             }
         }
         }
 
 
-        public Task ClearLog()
+        public async Task ClearLog()
         {
         {
-            return _repo.DeleteAll();
+            await _repo.DeleteAll();
+            EventHelper.FireEventIfNotNull(LogReset, this, EventArgs.Empty, _logger);
         }
         }
 
 
         public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request)
         public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request)
@@ -189,5 +227,55 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
                 _config.SaveAutoOrganizeOptions(options);
                 _config.SaveAutoOrganizeOptions(options);
             }
             }
         }
         }
+
+        /// <summary>
+        /// Attempts to add a an item to the list of currently processed items.
+        /// </summary>
+        /// <param name="result">The result item.</param>
+        /// <param name="isNewItem">Passing true will notify the client to reload all items, otherwise only a single item will be refreshed.</param>
+        /// <returns>True if the item was added, False if the item is already contained in the list.</returns>
+        public bool AddToInProgressList(FileOrganizationResult result, bool isNewItem)
+        {
+            if (string.IsNullOrWhiteSpace(result.Id))
+            {
+                result.Id = result.OriginalPath.GetMD5().ToString("N");
+            }
+
+            if (!_inProgressItemIds.TryAdd(result.Id, false))
+            {
+                return false;
+            }
+
+            result.IsInProgress = true;
+
+            if (isNewItem)
+            {
+                EventHelper.FireEventIfNotNull(ItemAdded, this, new GenericEventArgs<FileOrganizationResult>(result), _logger);
+            }
+            else
+            {
+                EventHelper.FireEventIfNotNull(ItemUpdated, this, new GenericEventArgs<FileOrganizationResult>(result), _logger);
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Removes an item from the list of currently processed items.
+        /// </summary>
+        /// <param name="result">The result item.</param>
+        /// <returns>True if the item was removed, False if the item was not contained in the list.</returns>
+        public bool RemoveFromInprogressList(FileOrganizationResult result)
+        {
+            bool itemValue;
+            var retval = _inProgressItemIds.TryRemove(result.Id, out itemValue);
+
+            result.IsInProgress = false;
+
+            EventHelper.FireEventIfNotNull(ItemUpdated, this, new GenericEventArgs<FileOrganizationResult>(result), _logger);
+
+            return retval;
+        }
+
     }
     }
 }
 }

+ 18 - 2
MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -428,8 +428,24 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
 
             if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
             if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
                 string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase) ||
                 string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase) ||
-                localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1 ||
-                localPath.IndexOf("dashboard/", StringComparison.OrdinalIgnoreCase) != -1)
+                localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1)
+            {
+                httpRes.StatusCode = 200;
+                httpRes.ContentType = "text/html";
+                var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase)
+                    .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase);
+
+                if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
+                {
+                    httpRes.Write("<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + newUrl + "\">" + newUrl + "</a></body></html>");
+
+                    httpRes.Close();
+                    return;
+                }
+            }
+
+            if (localPath.IndexOf("dashboard/", StringComparison.OrdinalIgnoreCase) != -1 && 
+                localPath.IndexOf("web/dashboard", StringComparison.OrdinalIgnoreCase) == -1)
             {
             {
                 httpRes.StatusCode = 200;
                 httpRes.StatusCode = 200;
                 httpRes.ContentType = "text/html";
                 httpRes.ContentType = "text/html";

+ 21 - 19
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -172,27 +172,29 @@ namespace MediaBrowser.Server.Implementations.IO
             }
             }
         }
         }
 
 
-        public void Start()
+        private bool IsLibraryMonitorEnabaled(BaseItem item)
         {
         {
-            if (EnableLibraryMonitor)
+            var options = LibraryManager.GetLibraryOptions(item);
+
+            if (options != null && options.SchemaVersion >= 1)
             {
             {
-                StartInternal();
+                return options.EnableRealtimeMonitor;
             }
             }
+
+            return EnableLibraryMonitor;
         }
         }
 
 
-        /// <summary>
-        /// Starts this instance.
-        /// </summary>
-        private void StartInternal()
+        public void Start()
         {
         {
             LibraryManager.ItemAdded += LibraryManager_ItemAdded;
             LibraryManager.ItemAdded += LibraryManager_ItemAdded;
             LibraryManager.ItemRemoved += LibraryManager_ItemRemoved;
             LibraryManager.ItemRemoved += LibraryManager_ItemRemoved;
 
 
-            var pathsToWatch = new List<string> { LibraryManager.RootFolder.Path };
+            var pathsToWatch = new List<string> { };
 
 
             var paths = LibraryManager
             var paths = LibraryManager
                 .RootFolder
                 .RootFolder
                 .Children
                 .Children
+                .Where(IsLibraryMonitorEnabaled)
                 .OfType<Folder>()
                 .OfType<Folder>()
                 .SelectMany(f => f.PhysicalLocations)
                 .SelectMany(f => f.PhysicalLocations)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
@@ -213,6 +215,14 @@ namespace MediaBrowser.Server.Implementations.IO
             }
             }
         }
         }
 
 
+        private void StartWatching(BaseItem item)
+        {
+            if (IsLibraryMonitorEnabaled(item))
+            {
+                StartWatchingPath(item.Path);
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Handles the ItemRemoved event of the LibraryManager control.
         /// Handles the ItemRemoved event of the LibraryManager control.
         /// </summary>
         /// </summary>
@@ -235,7 +245,7 @@ namespace MediaBrowser.Server.Implementations.IO
         {
         {
             if (e.Item.GetParent() is AggregateFolder)
             if (e.Item.GetParent() is AggregateFolder)
             {
             {
-                StartWatchingPath(e.Item.Path);
+                StartWatching(e.Item);
             }
             }
         }
         }
 
 
@@ -382,14 +392,6 @@ namespace MediaBrowser.Server.Implementations.IO
             Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex);
             Logger.ErrorException("Error in Directory watcher for: " + dw.Path, ex);
 
 
             DisposeWatcher(dw);
             DisposeWatcher(dw);
-
-            if (ConfigurationManager.Configuration.EnableLibraryMonitor == AutoOnOff.Auto)
-            {
-                Logger.Info("Disabling realtime monitor to prevent future instability");
-
-                ConfigurationManager.Configuration.EnableLibraryMonitor = AutoOnOff.Disabled;
-                Stop();
-            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -420,8 +422,8 @@ namespace MediaBrowser.Server.Implementations.IO
 
 
             var filename = Path.GetFileName(path);
             var filename = Path.GetFileName(path);
 
 
-            var monitorPath = !string.IsNullOrEmpty(filename) && 
-                !_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) && 
+            var monitorPath = !string.IsNullOrEmpty(filename) &&
+                !_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) &&
                 !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase);
                 !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase);
 
 
             // Ignore certain files
             // Ignore certain files

+ 0 - 6
MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -203,12 +203,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
         /// <returns>Video.</returns>
         /// <returns>Video.</returns>
         protected override Video Resolve(ItemResolveArgs args)
         protected override Video Resolve(ItemResolveArgs args)
         {
         {
-            if (args.Path != null && args.Path.IndexOf("disney", StringComparison.OrdinalIgnoreCase) != -1)
-            {
-                var b = true;
-                var a = b;
-            }
-
             var collectionType = args.GetCollectionType();
             var collectionType = args.GetCollectionType();
 
 
             if (IsInvalid(args.Parent, collectionType))
             if (IsInvalid(args.Parent, collectionType))

+ 2 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -149,6 +149,7 @@
     <Compile Include="EntryPoints\UsageReporter.cs" />
     <Compile Include="EntryPoints\UsageReporter.cs" />
     <Compile Include="FileOrganization\EpisodeFileOrganizer.cs" />
     <Compile Include="FileOrganization\EpisodeFileOrganizer.cs" />
     <Compile Include="FileOrganization\Extensions.cs" />
     <Compile Include="FileOrganization\Extensions.cs" />
+    <Compile Include="FileOrganization\FileOrganizationNotifier.cs" />
     <Compile Include="FileOrganization\FileOrganizationService.cs" />
     <Compile Include="FileOrganization\FileOrganizationService.cs" />
     <Compile Include="FileOrganization\NameUtils.cs" />
     <Compile Include="FileOrganization\NameUtils.cs" />
     <Compile Include="FileOrganization\TvFolderOrganizer.cs" />
     <Compile Include="FileOrganization\TvFolderOrganizer.cs" />
@@ -271,6 +272,7 @@
     <Compile Include="Sorting\StartDateComparer.cs" />
     <Compile Include="Sorting\StartDateComparer.cs" />
     <Compile Include="Sync\SyncHelper.cs" />
     <Compile Include="Sync\SyncHelper.cs" />
     <Compile Include="Sync\SyncJobOptions.cs" />
     <Compile Include="Sync\SyncJobOptions.cs" />
+    <Compile Include="Sync\SyncNotificationEntryPoint.cs" />
     <Compile Include="UserViews\CollectionFolderImageProvider.cs" />
     <Compile Include="UserViews\CollectionFolderImageProvider.cs" />
     <Compile Include="UserViews\DynamicImageProvider.cs" />
     <Compile Include="UserViews\DynamicImageProvider.cs" />
     <Compile Include="News\NewsEntryPoint.cs" />
     <Compile Include="News\NewsEntryPoint.cs" />

+ 1 - 1
MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs

@@ -123,7 +123,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                 {
                 {
                     if (extractImages)
                     if (extractImages)
                     {
                     {
-                        if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso || video.VideoType == VideoType.BluRay)
+                        if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso || video.VideoType == VideoType.BluRay || video.VideoType == VideoType.Dvd)
                         {
                         {
                             continue;
                             continue;
                         }
                         }

+ 1 - 2
MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs

@@ -313,8 +313,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
                     if (Folder.IsPathOffline(path))
                     if (Folder.IsPathOffline(path))
                     {
                     {
-                        libraryItem.IsOffline = true;
-                        await libraryItem.UpdateToRepository(ItemUpdateType.None, cancellationToken).ConfigureAwait(false);
+                        await libraryItem.UpdateIsOffline(true).ConfigureAwait(false);
                         continue;
                         continue;
                     }
                     }
 
 

+ 6 - 3
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -211,7 +211,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
             _connection.AddColumn(Logger, "TypedBaseItems", "ProductionYear", "INT");
             _connection.AddColumn(Logger, "TypedBaseItems", "ProductionYear", "INT");
             _connection.AddColumn(Logger, "TypedBaseItems", "ParentId", "GUID");
             _connection.AddColumn(Logger, "TypedBaseItems", "ParentId", "GUID");
             _connection.AddColumn(Logger, "TypedBaseItems", "Genres", "Text");
             _connection.AddColumn(Logger, "TypedBaseItems", "Genres", "Text");
-            _connection.AddColumn(Logger, "TypedBaseItems", "ParentalRatingValue", "INT");
             _connection.AddColumn(Logger, "TypedBaseItems", "SchemaVersion", "INT");
             _connection.AddColumn(Logger, "TypedBaseItems", "SchemaVersion", "INT");
             _connection.AddColumn(Logger, "TypedBaseItems", "SortName", "Text");
             _connection.AddColumn(Logger, "TypedBaseItems", "SortName", "Text");
             _connection.AddColumn(Logger, "TypedBaseItems", "RunTimeTicks", "BIGINT");
             _connection.AddColumn(Logger, "TypedBaseItems", "RunTimeTicks", "BIGINT");
@@ -488,7 +487,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 "ProductionYear",
                 "ProductionYear",
                 "ParentId",
                 "ParentId",
                 "Genres",
                 "Genres",
-                "ParentalRatingValue",
                 "InheritedParentalRatingValue",
                 "InheritedParentalRatingValue",
                 "SchemaVersion",
                 "SchemaVersion",
                 "SortName",
                 "SortName",
@@ -795,7 +793,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
                     }
                     }
 
 
                     _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Genres.ToArray());
                     _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Genres.ToArray());
-                    _saveItemCommand.GetParameter(index++).Value = item.GetParentalRatingValue() ?? 0;
                     _saveItemCommand.GetParameter(index++).Value = item.GetInheritedParentalRatingValue() ?? 0;
                     _saveItemCommand.GetParameter(index++).Value = item.GetInheritedParentalRatingValue() ?? 0;
 
 
                     _saveItemCommand.GetParameter(index++).Value = LatestSchemaVersion;
                     _saveItemCommand.GetParameter(index++).Value = LatestSchemaVersion;
@@ -4286,6 +4283,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
             var index = 0;
             var index = 0;
             foreach (var image in images)
             foreach (var image in images)
             {
             {
+                if (string.IsNullOrWhiteSpace(image.Path))
+                {
+                    // Invalid
+                    continue;
+                }
+
                 _saveImagesCommand.GetParameter(0).Value = itemId;
                 _saveImagesCommand.GetParameter(0).Value = itemId;
                 _saveImagesCommand.GetParameter(1).Value = image.Type;
                 _saveImagesCommand.GetParameter(1).Value = image.Type;
                 _saveImagesCommand.GetParameter(2).Value = image.Path;
                 _saveImagesCommand.GetParameter(2).Value = image.Path;

+ 9 - 2
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -1869,10 +1869,17 @@ namespace MediaBrowser.Server.Implementations.Session
             return GetSessionByAuthenticationToken(info, deviceId, remoteEndpoint, null);
             return GetSessionByAuthenticationToken(info, deviceId, remoteEndpoint, null);
         }
         }
 
 
-        public Task SendMessageToUserSessions<T>(string userId, string name, T data,
+        public Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken)
+        {
+            var adminUserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id.ToString("N")).ToList();
+
+            return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken);
+        }
+
+        public Task SendMessageToUserSessions<T>(List<string> userIds, string name, T data,
             CancellationToken cancellationToken)
             CancellationToken cancellationToken)
         {
         {
-            var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null && i.ContainsUser(userId)).ToList();
+            var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null && userIds.Any(i.ContainsUser)).ToList();
 
 
             var tasks = sessions.Select(session => Task.Run(async () =>
             var tasks = sessions.Select(session => Task.Run(async () =>
             {
             {

+ 5 - 0
MediaBrowser.Server.Implementations/Sync/AppSyncProvider.cs

@@ -85,6 +85,11 @@ namespace MediaBrowser.Server.Implementations.Sync
                 {
                 {
                     Name = "Low",
                     Name = "Low",
                     Id = "low"
                     Id = "low"
+                },
+                new SyncQualityOption
+                {
+                    Name = "Custom",
+                    Id = "custom"
                 }
                 }
             };
             };
         }
         }

+ 48 - 0
MediaBrowser.Server.Implementations/Sync/SyncNotificationEntryPoint.cs

@@ -0,0 +1,48 @@
+using System.Threading;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Controller.Sync;
+using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Sync;
+
+namespace MediaBrowser.Server.Implementations.Sync
+{
+    public class SyncNotificationEntryPoint : IServerEntryPoint
+    {
+        private readonly ISessionManager _sessionManager;
+        private readonly ISyncManager _syncManager;
+
+        public SyncNotificationEntryPoint(ISyncManager syncManager, ISessionManager sessionManager)
+        {
+            _syncManager = syncManager;
+            _sessionManager = sessionManager;
+        }
+
+        public void Run()
+        {
+            _syncManager.SyncJobItemUpdated += _syncManager_SyncJobItemUpdated;
+        }
+
+        private async void _syncManager_SyncJobItemUpdated(object sender, GenericEventArgs<SyncJobItem> e)
+        {
+            var item = e.Argument;
+
+            if (item.Status == SyncJobItemStatus.ReadyToTransfer)
+            {
+                try
+                {
+                    await _sessionManager.SendMessageToUserDeviceSessions(item.TargetId, "SyncJobItemReady", item, CancellationToken.None).ConfigureAwait(false);
+                }
+                catch
+                {
+
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            _syncManager.SyncJobItemUpdated -= _syncManager_SyncJobItemUpdated;
+        }
+    }
+}

+ 71 - 323
MediaBrowser.Server.Mac/Emby.Server.Mac.csproj

@@ -390,6 +390,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\autoorganizetv.html">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\autoorganizetv.html">
       <Link>Resources\dashboard-ui\autoorganizetv.html</Link>
       <Link>Resources\dashboard-ui\autoorganizetv.html</Link>
     </BundleResource>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\camerauploadsettings.html">
+      <Link>Resources\dashboard-ui\camerauploadsettings.html</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\channelitems.html">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\channelitems.html">
       <Link>Resources\dashboard-ui\channelitems.html</Link>
       <Link>Resources\dashboard-ui\channelitems.html</Link>
     </BundleResource>
     </BundleResource>
@@ -1128,9 +1131,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\multidownload.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\multidownload.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\multidownload.js</Link>
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\multidownload.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\objectassign.js">
-      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\objectassign.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playmenu.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playmenu.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playmenu.js</Link>
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playmenu.js</Link>
     </BundleResource>
     </BundleResource>
@@ -1155,6 +1155,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js</Link>
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js</Link>
     </BundleResource>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\thememediaplayer.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\thememediaplayer.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\visibleinviewport.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\visibleinviewport.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\visibleinviewport.js</Link>
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\visibleinviewport.js</Link>
     </BundleResource>
     </BundleResource>
@@ -1566,6 +1569,15 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playlisteditor\playlisteditor.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playlisteditor\playlisteditor.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playlisteditor\playlisteditor.js</Link>
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playlisteditor\playlisteditor.js</Link>
     </BundleResource>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\polyfills\array.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\polyfills\array.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\polyfills\bind.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\polyfills\bind.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\polyfills\objectassign.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\polyfills\objectassign.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\prompt\nativeprompt.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\prompt\nativeprompt.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\prompt\nativeprompt.js</Link>
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\prompt\nativeprompt.js</Link>
     </BundleResource>
     </BundleResource>
@@ -1773,6 +1785,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.template.html">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.template.html">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.template.html</Link>
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.template.html</Link>
     </BundleResource>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sync\sync.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sync\sync.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\toast\toast.css">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\toast\toast.css">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\toast\toast.css</Link>
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\toast\toast.css</Link>
     </BundleResource>
     </BundleResource>
@@ -1860,36 +1875,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fetch\fetch.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fetch\fetch.js">
       <Link>Resources\dashboard-ui\bower_components\fetch\fetch.js</Link>
       <Link>Resources\dashboard-ui\bower_components\fetch\fetch.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\.bower.json">
-      <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\.bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\CONTRIBUTING.md">
-      <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\CONTRIBUTING.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\FAQ.md">
-      <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\FAQ.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\README.md">
-      <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\README.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\bower.json">
-      <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\fingerprint2.js">
-      <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\fingerprint2.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\gulpfile.js">
-      <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\gulpfile.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\index.html">
-      <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\package.json">
-      <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\package.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\dist\fingerprint2.min.js">
-      <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\dist\fingerprint2.min.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\font-roboto\.bower.json">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\font-roboto\.bower.json">
       <Link>Resources\dashboard-ui\bower_components\font-roboto\.bower.json</Link>
       <Link>Resources\dashboard-ui\bower_components\font-roboto\.bower.json</Link>
     </BundleResource>
     </BundleResource>
@@ -2094,45 +2079,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\test\index.html">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\test\index.html">
       <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\test\index.html</Link>
       <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\test\index.html</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\.bower.json">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\.bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\.gitignore">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\.gitignore</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\.travis.yml">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\.travis.yml</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\CONTRIBUTING.md">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\CONTRIBUTING.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\README.md">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\README.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\bower.json">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\hero.svg">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\hero.svg</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\index.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\iron-autogrow-textarea.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\iron-autogrow-textarea.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\.github\ISSUE_TEMPLATE.md">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\.github\ISSUE_TEMPLATE.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\demo\index.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\demo\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\test\basic.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\test\basic.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-autogrow-textarea\test\index.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-autogrow-textarea\test\index.html</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\.bower.json">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\.bower.json">
       <Link>Resources\dashboard-ui\bower_components\iron-behaviors\.bower.json</Link>
       <Link>Resources\dashboard-ui\bower_components\iron-behaviors\.bower.json</Link>
     </BundleResource>
     </BundleResource>
@@ -2502,51 +2448,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\test\iron-iconset-svg.html">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-iconset-svg\test\iron-iconset-svg.html">
       <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\test\iron-iconset-svg.html</Link>
       <Link>Resources\dashboard-ui\bower_components\iron-iconset-svg\test\iron-iconset-svg.html</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\.bower.json">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\.bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\.gitignore">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\.gitignore</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\.travis.yml">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\.travis.yml</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\CONTRIBUTING.md">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\CONTRIBUTING.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\README.md">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\README.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\bower.json">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\hero.svg">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\hero.svg</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\index.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\iron-input.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\iron-input.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\.github\ISSUE_TEMPLATE.md">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\.github\ISSUE_TEMPLATE.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\demo\index.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\demo\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\test\disabled-input.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\test\disabled-input.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\test\index.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\test\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\test\iron-input.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\test\iron-input.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\test\letters-only.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-input\test\letters-only.html</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\.bower.json">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\.bower.json">
       <Link>Resources\dashboard-ui\bower_components\iron-meta\.bower.json</Link>
       <Link>Resources\dashboard-ui\bower_components\iron-meta\.bower.json</Link>
     </BundleResource>
     </BundleResource>
@@ -2574,6 +2475,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\iron-meta.html">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\iron-meta.html">
       <Link>Resources\dashboard-ui\bower_components\iron-meta\iron-meta.html</Link>
       <Link>Resources\dashboard-ui\bower_components\iron-meta\iron-meta.html</Link>
     </BundleResource>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\.github\ISSUE_TEMPLATE.md">
+      <Link>Resources\dashboard-ui\bower_components\iron-meta\.github\ISSUE_TEMPLATE.md</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\demo\index.html">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\demo\index.html">
       <Link>Resources\dashboard-ui\bower_components\iron-meta\demo\index.html</Link>
       <Link>Resources\dashboard-ui\bower_components\iron-meta\demo\index.html</Link>
     </BundleResource>
     </BundleResource>
@@ -2586,45 +2490,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\test\iron-meta.html">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\test\iron-meta.html">
       <Link>Resources\dashboard-ui\bower_components\iron-meta\test\iron-meta.html</Link>
       <Link>Resources\dashboard-ui\bower_components\iron-meta\test\iron-meta.html</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\.bower.json">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\.bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\.gitignore">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\.gitignore</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\.travis.yml">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\.travis.yml</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\CONTRIBUTING.md">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\CONTRIBUTING.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\README.md">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\README.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\bower.json">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\index.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\iron-range-behavior.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\iron-range-behavior.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\.github\ISSUE_TEMPLATE.md">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\.github\ISSUE_TEMPLATE.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\demo\index.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\demo\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\test\basic.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\test\basic.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\test\index.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\test\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\test\x-progressbar.html">
-      <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\test\x-progressbar.html</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\.bower.json">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\.bower.json">
       <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\.bower.json</Link>
       <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\.bower.json</Link>
     </BundleResource>
     </BundleResource>
@@ -3252,126 +3117,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\test\index.html">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\test\index.html">
       <Link>Resources\dashboard-ui\bower_components\paper-icon-button\test\index.html</Link>
       <Link>Resources\dashboard-ui\bower_components\paper-icon-button\test\index.html</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\.bower.json">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\.bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\.gitignore">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\.gitignore</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\.travis.yml">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\.travis.yml</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\CONTRIBUTING.md">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\CONTRIBUTING.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\README.md">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\README.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\all-imports.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\all-imports.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\bower.json">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\hero.svg">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\hero.svg</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\index.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\paper-input-addon-behavior.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\paper-input-addon-behavior.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\paper-input-behavior.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\paper-input-behavior.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\paper-input-char-counter.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\paper-input-char-counter.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\paper-input-container.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\paper-input-container.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\paper-input-error.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\paper-input-error.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\paper-input.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\paper-input.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\paper-textarea.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\paper-textarea.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\.github\ISSUE_TEMPLATE.md">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\.github\ISSUE_TEMPLATE.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\demo\index.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\demo\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\demo\ssn-input.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\demo\ssn-input.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\demo\ssn-validator.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\demo\ssn-validator.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\test\index.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\test\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\test\letters-only.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\test\letters-only.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\test\paper-input-char-counter.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\test\paper-input-char-counter.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\test\paper-input-container.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\test\paper-input-container.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\test\paper-input-error.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\test\paper-input-error.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\test\paper-input.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\test\paper-input.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\test\paper-textarea.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-input\test\paper-textarea.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\.bower.json">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\.bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\.gitignore">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\.gitignore</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\.travis.yml">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\.travis.yml</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\CONTRIBUTING.md">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\CONTRIBUTING.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\README.md">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\README.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\bower.json">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\bower.json</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\hero.svg">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\hero.svg</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\index.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\paper-progress.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\paper-progress.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\.github\ISSUE_TEMPLATE.md">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\.github\ISSUE_TEMPLATE.md</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\demo\index.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\demo\index.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\test\basic.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\test\basic.html</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\test\index.html">
-      <Link>Resources\dashboard-ui\bower_components\paper-progress\test\index.html</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\.bower.json">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\.bower.json">
       <Link>Resources\dashboard-ui\bower_components\paper-ripple\.bower.json</Link>
       <Link>Resources\dashboard-ui\bower_components\paper-ripple\.bower.json</Link>
     </BundleResource>
     </BundleResource>
@@ -3729,6 +3474,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\tvproviders\xmltv.template.html">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\tvproviders\xmltv.template.html">
       <Link>Resources\dashboard-ui\components\tvproviders\xmltv.template.html</Link>
       <Link>Resources\dashboard-ui\components\tvproviders\xmltv.template.html</Link>
     </BundleResource>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\autoorganizetable.css">
+      <Link>Resources\dashboard-ui\css\autoorganizetable.css</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\chromecast.css">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\chromecast.css">
       <Link>Resources\dashboard-ui\css\chromecast.css</Link>
       <Link>Resources\dashboard-ui\css\chromecast.css</Link>
     </BundleResource>
     </BundleResource>
@@ -3801,6 +3549,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\rotten.png">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\rotten.png">
       <Link>Resources\dashboard-ui\css\images\rotten.png</Link>
       <Link>Resources\dashboard-ui\css\images\rotten.png</Link>
     </BundleResource>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\throbber.gif">
+      <Link>Resources\dashboard-ui\css\images\throbber.gif</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\userflyoutdefault.png">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\userflyoutdefault.png">
       <Link>Resources\dashboard-ui\css\images\userflyoutdefault.png</Link>
       <Link>Resources\dashboard-ui\css\images\userflyoutdefault.png</Link>
     </BundleResource>
     </BundleResource>
@@ -3987,6 +3738,48 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\userdata\password.png">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\userdata\password.png">
       <Link>Resources\dashboard-ui\css\images\userdata\password.png</Link>
       <Link>Resources\dashboard-ui\css\images\userdata\password.png</Link>
     </BundleResource>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\aboutpage.js">
+      <Link>Resources\dashboard-ui\dashboard\aboutpage.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\autoorganizelog.js">
+      <Link>Resources\dashboard-ui\dashboard\autoorganizelog.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\autoorganizesmart.js">
+      <Link>Resources\dashboard-ui\dashboard\autoorganizesmart.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\autoorganizetv.js">
+      <Link>Resources\dashboard-ui\dashboard\autoorganizetv.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\cinemamodeconfiguration.js">
+      <Link>Resources\dashboard-ui\dashboard\cinemamodeconfiguration.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\dashboardgeneral.js">
+      <Link>Resources\dashboard-ui\dashboard\dashboardgeneral.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\dashboardhosting.js">
+      <Link>Resources\dashboard-ui\dashboard\dashboardhosting.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\devicesupload.js">
+      <Link>Resources\dashboard-ui\dashboard\devicesupload.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\librarydisplay.js">
+      <Link>Resources\dashboard-ui\dashboard\librarydisplay.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\librarysettings.js">
+      <Link>Resources\dashboard-ui\dashboard\librarysettings.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\livetvtunerprovider-satip.js">
+      <Link>Resources\dashboard-ui\dashboard\livetvtunerprovider-satip.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\logpage.js">
+      <Link>Resources\dashboard-ui\dashboard\logpage.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\wizardcomponents.js">
+      <Link>Resources\dashboard-ui\dashboard\wizardcomponents.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dashboard\wizardfinishpage.js">
+      <Link>Resources\dashboard-ui\dashboard\wizardfinishpage.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\android\android.css">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\android\android.css">
       <Link>Resources\dashboard-ui\devices\android\android.css</Link>
       <Link>Resources\dashboard-ui\devices\android\android.css</Link>
     </BundleResource>
     </BundleResource>
@@ -4008,9 +3801,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\selectmenu.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\selectmenu.js">
       <Link>Resources\dashboard-ui\legacy\selectmenu.js</Link>
       <Link>Resources\dashboard-ui\legacy\selectmenu.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\aboutpage.js">
-      <Link>Resources\dashboard-ui\scripts\aboutpage.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\addpluginpage.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\addpluginpage.js">
       <Link>Resources\dashboard-ui\scripts\addpluginpage.js</Link>
       <Link>Resources\dashboard-ui\scripts\addpluginpage.js</Link>
     </BundleResource>
     </BundleResource>
@@ -4020,14 +3810,8 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\autobackdrops.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\autobackdrops.js">
       <Link>Resources\dashboard-ui\scripts\autobackdrops.js</Link>
       <Link>Resources\dashboard-ui\scripts\autobackdrops.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\autoorganizelog.js">
-      <Link>Resources\dashboard-ui\scripts\autoorganizelog.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\autoorganizesmart.js">
-      <Link>Resources\dashboard-ui\scripts\autoorganizesmart.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\autoorganizetv.js">
-      <Link>Resources\dashboard-ui\scripts\autoorganizetv.js</Link>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\camerauploadsettings.js">
+      <Link>Resources\dashboard-ui\scripts\camerauploadsettings.js</Link>
     </BundleResource>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\channelitems.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\channelitems.js">
       <Link>Resources\dashboard-ui\scripts\channelitems.js</Link>
       <Link>Resources\dashboard-ui\scripts\channelitems.js</Link>
@@ -4041,18 +3825,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\chromecast.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\chromecast.js">
       <Link>Resources\dashboard-ui\scripts\chromecast.js</Link>
       <Link>Resources\dashboard-ui\scripts\chromecast.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\cinemamodeconfiguration.js">
-      <Link>Resources\dashboard-ui\scripts\cinemamodeconfiguration.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\connectlogin.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\connectlogin.js">
       <Link>Resources\dashboard-ui\scripts\connectlogin.js</Link>
       <Link>Resources\dashboard-ui\scripts\connectlogin.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\dashboardgeneral.js">
-      <Link>Resources\dashboard-ui\scripts\dashboardgeneral.js</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\dashboardhosting.js">
-      <Link>Resources\dashboard-ui\scripts\dashboardhosting.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\dashboardpage.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\dashboardpage.js">
       <Link>Resources\dashboard-ui\scripts\dashboardpage.js</Link>
       <Link>Resources\dashboard-ui\scripts\dashboardpage.js</Link>
     </BundleResource>
     </BundleResource>
@@ -4062,9 +3837,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\devices.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\devices.js">
       <Link>Resources\dashboard-ui\scripts\devices.js</Link>
       <Link>Resources\dashboard-ui\scripts\devices.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\devicesupload.js">
-      <Link>Resources\dashboard-ui\scripts\devicesupload.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\dlnaprofile.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\dlnaprofile.js">
       <Link>Resources\dashboard-ui\scripts\dlnaprofile.js</Link>
       <Link>Resources\dashboard-ui\scripts\dlnaprofile.js</Link>
     </BundleResource>
     </BundleResource>
@@ -4140,18 +3912,12 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarybrowser.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarybrowser.js">
       <Link>Resources\dashboard-ui\scripts\librarybrowser.js</Link>
       <Link>Resources\dashboard-ui\scripts\librarybrowser.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarydisplay.js">
-      <Link>Resources\dashboard-ui\scripts\librarydisplay.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarymenu.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarymenu.js">
       <Link>Resources\dashboard-ui\scripts\librarymenu.js</Link>
       <Link>Resources\dashboard-ui\scripts\librarymenu.js</Link>
     </BundleResource>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarypathmapping.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarypathmapping.js">
       <Link>Resources\dashboard-ui\scripts\librarypathmapping.js</Link>
       <Link>Resources\dashboard-ui\scripts\librarypathmapping.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarysettings.js">
-      <Link>Resources\dashboard-ui\scripts\librarysettings.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvchannel.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvchannel.js">
       <Link>Resources\dashboard-ui\scripts\livetvchannel.js</Link>
       <Link>Resources\dashboard-ui\scripts\livetvchannel.js</Link>
     </BundleResource>
     </BundleResource>
@@ -4197,18 +3963,12 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvtunerprovider-m3u.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvtunerprovider-m3u.js">
       <Link>Resources\dashboard-ui\scripts\livetvtunerprovider-m3u.js</Link>
       <Link>Resources\dashboard-ui\scripts\livetvtunerprovider-m3u.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvtunerprovider-satip.js">
-      <Link>Resources\dashboard-ui\scripts\livetvtunerprovider-satip.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\localsync.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\localsync.js">
       <Link>Resources\dashboard-ui\scripts\localsync.js</Link>
       <Link>Resources\dashboard-ui\scripts\localsync.js</Link>
     </BundleResource>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\loginpage.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\loginpage.js">
       <Link>Resources\dashboard-ui\scripts\loginpage.js</Link>
       <Link>Resources\dashboard-ui\scripts\loginpage.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\logpage.js">
-      <Link>Resources\dashboard-ui\scripts\logpage.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\mediacontroller.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\mediacontroller.js">
       <Link>Resources\dashboard-ui\scripts\mediacontroller.js</Link>
       <Link>Resources\dashboard-ui\scripts\mediacontroller.js</Link>
     </BundleResource>
     </BundleResource>
@@ -4371,9 +4131,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\supporterkeypage.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\supporterkeypage.js">
       <Link>Resources\dashboard-ui\scripts\supporterkeypage.js</Link>
       <Link>Resources\dashboard-ui\scripts\supporterkeypage.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\sync.js">
-      <Link>Resources\dashboard-ui\scripts\sync.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\syncactivity.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\syncactivity.js">
       <Link>Resources\dashboard-ui\scripts\syncactivity.js</Link>
       <Link>Resources\dashboard-ui\scripts\syncactivity.js</Link>
     </BundleResource>
     </BundleResource>
@@ -4386,9 +4143,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\taskbutton.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\taskbutton.js">
       <Link>Resources\dashboard-ui\scripts\taskbutton.js</Link>
       <Link>Resources\dashboard-ui\scripts\taskbutton.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\thememediaplayer.js">
-      <Link>Resources\dashboard-ui\scripts\thememediaplayer.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\tvgenres.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\tvgenres.js">
       <Link>Resources\dashboard-ui\scripts\tvgenres.js</Link>
       <Link>Resources\dashboard-ui\scripts\tvgenres.js</Link>
     </BundleResource>
     </BundleResource>
@@ -4431,15 +4185,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardagreement.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardagreement.js">
       <Link>Resources\dashboard-ui\scripts\wizardagreement.js</Link>
       <Link>Resources\dashboard-ui\scripts\wizardagreement.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardcomponents.js">
-      <Link>Resources\dashboard-ui\scripts\wizardcomponents.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardcontroller.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardcontroller.js">
       <Link>Resources\dashboard-ui\scripts\wizardcontroller.js</Link>
       <Link>Resources\dashboard-ui\scripts\wizardcontroller.js</Link>
     </BundleResource>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardfinishpage.js">
-      <Link>Resources\dashboard-ui\scripts\wizardfinishpage.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardlivetvguide.js">
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardlivetvguide.js">
       <Link>Resources\dashboard-ui\scripts\wizardlivetvguide.js</Link>
       <Link>Resources\dashboard-ui\scripts\wizardlivetvguide.js</Link>
     </BundleResource>
     </BundleResource>

+ 4 - 25
MediaBrowser.Server.Mono/Native/BaseMonoApp.cs

@@ -132,7 +132,7 @@ namespace MediaBrowser.Server.Mono.Native
         {
         {
             get
             get
             {
             {
-				return Environment.OperatingSystem != Startup.Common.OperatingSystem.Osx;
+                return Environment.OperatingSystem != Startup.Common.OperatingSystem.Osx;
             }
             }
         }
         }
 
 
@@ -187,7 +187,7 @@ namespace MediaBrowser.Server.Mono.Native
             {
             {
                 info.SystemArchitecture = Architecture.X64;
                 info.SystemArchitecture = Architecture.X64;
             }
             }
-            else 
+            else
             {
             {
                 info.SystemArchitecture = Architecture.X86;
                 info.SystemArchitecture = Architecture.X86;
             }
             }
@@ -273,32 +273,11 @@ namespace MediaBrowser.Server.Mono.Native
                     break;
                     break;
             }
             }
 
 
-            info.DownloadUrls = GetDownloadUrls(environment);
+            // No version available - user requirement
+            info.DownloadUrls = new string[] { };
 
 
             return info;
             return info;
         }
         }
-
-        private static string[] GetDownloadUrls(NativeEnvironment environment)
-        {
-            switch (environment.OperatingSystem)
-            {
-                case OperatingSystem.Linux:
-
-                    switch (environment.SystemArchitecture)
-                    {
-                        case Architecture.X64:
-                            return new[]
-                            {
-                                "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z"
-                            };
-                    }
-                    break;
-            }
-
-            // No version available 
-            return new string[] { };
-        }
-
     }
     }
 
 
     public class NullPowerManagement : IPowerManagement
     public class NullPowerManagement : IPowerManagement

+ 1 - 2
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -385,8 +385,7 @@ namespace MediaBrowser.Server.Startup.Common
                 new OmdbEpisodeProviderMigration(ServerConfigurationManager),
                 new OmdbEpisodeProviderMigration(ServerConfigurationManager),
                 new MovieDbEpisodeProviderMigration(ServerConfigurationManager),
                 new MovieDbEpisodeProviderMigration(ServerConfigurationManager),
                 new DbMigration(ServerConfigurationManager, TaskManager),
                 new DbMigration(ServerConfigurationManager, TaskManager),
-                new FolderViewSettingMigration(ServerConfigurationManager, UserManager),
-                new CollectionGroupingMigration(ServerConfigurationManager, UserManager),
+                new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename),
                 new CollectionsViewMigration(ServerConfigurationManager, UserManager)
                 new CollectionsViewMigration(ServerConfigurationManager, UserManager)
             };
             };
 
 

+ 1 - 2
MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj

@@ -70,13 +70,12 @@
     <Compile Include="FFMpeg\FFMpegInfo.cs" />
     <Compile Include="FFMpeg\FFMpegInfo.cs" />
     <Compile Include="INativeApp.cs" />
     <Compile Include="INativeApp.cs" />
     <Compile Include="MbLinkShortcutHandler.cs" />
     <Compile Include="MbLinkShortcutHandler.cs" />
-    <Compile Include="Migrations\CollectionGroupingMigration.cs" />
     <Compile Include="Migrations\CollectionsViewMigration.cs" />
     <Compile Include="Migrations\CollectionsViewMigration.cs" />
-    <Compile Include="Migrations\FolderViewSettingMigration.cs" />
     <Compile Include="Migrations\IVersionMigration.cs" />
     <Compile Include="Migrations\IVersionMigration.cs" />
     <Compile Include="Migrations\DbMigration.cs" />
     <Compile Include="Migrations\DbMigration.cs" />
     <Compile Include="Migrations\MovieDbEpisodeProviderMigration.cs" />
     <Compile Include="Migrations\MovieDbEpisodeProviderMigration.cs" />
     <Compile Include="Migrations\OmdbEpisodeProviderMigration.cs" />
     <Compile Include="Migrations\OmdbEpisodeProviderMigration.cs" />
+    <Compile Include="Migrations\UpdateLevelMigration.cs" />
     <Compile Include="NativeEnvironment.cs" />
     <Compile Include="NativeEnvironment.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="StartupOptions.cs" />
     <Compile Include="StartupOptions.cs" />

+ 0 - 40
MediaBrowser.Server.Startup.Common/Migrations/CollectionGroupingMigration.cs

@@ -1,40 +0,0 @@
-using System.Linq;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-
-namespace MediaBrowser.Server.Startup.Common.Migrations
-{
-    public class CollectionGroupingMigration : IVersionMigration
-    {
-        private readonly IServerConfigurationManager _config;
-        private readonly IUserManager _userManager;
-
-        public CollectionGroupingMigration(IServerConfigurationManager config, IUserManager userManager)
-        {
-            _config = config;
-            _userManager = userManager;
-        }
-
-        public void Run()
-        {
-            var migrationKey = this.GetType().Name;
-            var migrationKeyList = _config.Configuration.Migrations.ToList();
-
-            if (!migrationKeyList.Contains(migrationKey))
-            {
-                if (_config.Configuration.IsStartupWizardCompleted)
-                {
-                    if (_userManager.Users.Any(i => i.Configuration.GroupMoviesIntoBoxSets))
-                    {
-                        _config.Configuration.EnableGroupingIntoCollections = true;
-                    }
-                }
-
-                migrationKeyList.Add(migrationKey);
-                _config.Configuration.Migrations = migrationKeyList.ToArray();
-                _config.SaveConfiguration();
-            }
-
-        }
-    }
-}

+ 0 - 40
MediaBrowser.Server.Startup.Common/Migrations/FolderViewSettingMigration.cs

@@ -1,40 +0,0 @@
-using System.Linq;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-
-namespace MediaBrowser.Server.Startup.Common.Migrations
-{
-    public class FolderViewSettingMigration : IVersionMigration
-    {
-        private readonly IServerConfigurationManager _config;
-        private readonly IUserManager _userManager;
-
-        public FolderViewSettingMigration(IServerConfigurationManager config, IUserManager userManager)
-        {
-            _config = config;
-            _userManager = userManager;
-        }
-
-        public void Run()
-        {
-            var migrationKey = this.GetType().Name;
-            var migrationKeyList = _config.Configuration.Migrations.ToList();
-
-            if (!migrationKeyList.Contains(migrationKey))
-            {
-                if (_config.Configuration.IsStartupWizardCompleted)
-                {
-                    if (_userManager.Users.Any(i => i.Configuration.DisplayFoldersView))
-                    {
-                        _config.Configuration.EnableFolderView = true;
-                    }
-                }
-
-                migrationKeyList.Add(migrationKey);
-                _config.Configuration.Migrations = migrationKeyList.ToArray();
-                _config.SaveConfiguration();
-            }
-
-        }
-    }
-}

+ 97 - 0
MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs

@@ -0,0 +1,97 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Implementations.Updates;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Updates;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+    public class UpdateLevelMigration : IVersionMigration
+    {
+        private readonly IServerConfigurationManager _config;
+        private readonly IServerApplicationHost _appHost;
+        private readonly IHttpClient _httpClient;
+        private readonly IJsonSerializer _jsonSerializer;
+        private readonly string _releaseAssetFilename;
+
+        public UpdateLevelMigration(IServerConfigurationManager config, IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, string releaseAssetFilename)
+        {
+            _config = config;
+            _appHost = appHost;
+            _httpClient = httpClient;
+            _jsonSerializer = jsonSerializer;
+            _releaseAssetFilename = releaseAssetFilename;
+        }
+
+        public async void Run()
+        {
+            var lastVersion = _config.Configuration.LastVersion;
+            var currentVersion = _appHost.ApplicationVersion;
+
+            if (string.Equals(lastVersion, currentVersion.ToString(), StringComparison.OrdinalIgnoreCase))
+            {
+                return;
+            }
+
+            try
+            {
+                var updateLevel = _config.Configuration.SystemUpdateLevel;
+
+                // Go down a level
+                if (updateLevel == PackageVersionClass.Release)
+                {
+                    updateLevel = PackageVersionClass.Beta;
+                }
+                else if (updateLevel == PackageVersionClass.Beta)
+                {
+                    updateLevel = PackageVersionClass.Dev;
+                }
+                else if (updateLevel == PackageVersionClass.Dev)
+                {
+                    // It's already dev, there's nothing to check
+                    return;
+                }
+
+                await CheckVersion(currentVersion, updateLevel, CancellationToken.None).ConfigureAwait(false);
+            }
+            catch
+            {
+
+            }
+        }
+
+        private async Task CheckVersion(Version currentVersion, PackageVersionClass updateLevel, CancellationToken cancellationToken)
+        {
+            var result = await new GithubUpdater(_httpClient, _jsonSerializer, TimeSpan.FromMinutes(5))
+                .CheckForUpdateResult("MediaBrowser", "Emby", currentVersion, PackageVersionClass.Beta, _releaseAssetFilename, "MBServer", "Mbserver.zip",
+                    cancellationToken).ConfigureAwait(false);
+
+            if (result != null && result.IsUpdateAvailable)
+            {
+                _config.Configuration.SystemUpdateLevel = updateLevel;
+                _config.SaveConfiguration();
+                return;
+            }
+
+            // Go down a level
+            if (updateLevel == PackageVersionClass.Release)
+            {
+                updateLevel = PackageVersionClass.Beta;
+            }
+            else if (updateLevel == PackageVersionClass.Beta)
+            {
+                updateLevel = PackageVersionClass.Dev;
+            }
+            else
+            {
+                return;
+            }
+
+            await CheckVersion(currentVersion, updateLevel, cancellationToken).ConfigureAwait(false);
+        }
+    }
+}

+ 38 - 26
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -157,11 +157,21 @@ namespace MediaBrowser.WebDashboard.Api
             var creator = GetPackageCreator();
             var creator = GetPackageCreator();
             var directory = creator.DashboardUIPath;
             var directory = creator.DashboardUIPath;
 
 
-            var skipExtensions = GetUndeployedExtensions();
+            var skipExtensions = GetDeployIgnoreExtensions();
+            var skipNames = GetDeployIgnoreFilenames();
 
 
             return
             return
                 Directory.GetFiles(directory, "*", SearchOption.AllDirectories)
                 Directory.GetFiles(directory, "*", SearchOption.AllDirectories)
                 .Where(i => !skipExtensions.Contains(Path.GetExtension(i) ?? string.Empty, StringComparer.OrdinalIgnoreCase))
                 .Where(i => !skipExtensions.Contains(Path.GetExtension(i) ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+                .Where(i => !skipNames.Any(s =>
+                {
+                    if (s.Item2)
+                    {
+                        return string.Equals(s.Item1, Path.GetFileName(i), StringComparison.OrdinalIgnoreCase);
+                    }
+
+                    return (Path.GetFileName(i) ?? string.Empty).IndexOf(s.Item1, StringComparison.OrdinalIgnoreCase) != -1;
+                }))
                 .Select(i => i.Replace(directory, string.Empty, StringComparison.OrdinalIgnoreCase).Replace("\\", "/").TrimStart('/') + "?v=" + _appHost.ApplicationVersion.ToString())
                 .Select(i => i.Replace(directory, string.Empty, StringComparison.OrdinalIgnoreCase).Replace("\\", "/").TrimStart('/') + "?v=" + _appHost.ApplicationVersion.ToString())
                 .ToList();
                 .ToList();
         }
         }
@@ -300,7 +310,7 @@ namespace MediaBrowser.WebDashboard.Api
             return new PackageCreator(_fileSystem, _localization, Logger, _serverConfigurationManager, _jsonSerializer);
             return new PackageCreator(_fileSystem, _localization, Logger, _serverConfigurationManager, _jsonSerializer);
         }
         }
 
 
-        private List<string> GetUndeployedExtensions()
+        private List<string> GetDeployIgnoreExtensions()
         {
         {
             var list = new List<string>();
             var list = new List<string>();
 
 
@@ -315,6 +325,28 @@ namespace MediaBrowser.WebDashboard.Api
             return list;
             return list;
         }
         }
 
 
+        private List<Tuple<string,bool>> GetDeployIgnoreFilenames()
+        {
+            var list = new List<Tuple<string, bool>>();
+
+            list.Add(new Tuple<string, bool>("copying", true));
+            list.Add(new Tuple<string, bool>("license", true));
+            list.Add(new Tuple<string, bool>("license-mit", true));
+            list.Add(new Tuple<string, bool>("gitignore", false));
+            list.Add(new Tuple<string, bool>("npmignore", false));
+            list.Add(new Tuple<string, bool>("jshintrc", false));
+            list.Add(new Tuple<string, bool>("gruntfile", false));
+            list.Add(new Tuple<string, bool>("bowerrc", false));
+            list.Add(new Tuple<string, bool>("jscsrc", false));
+            list.Add(new Tuple<string, bool>("hero.svg", false));
+            list.Add(new Tuple<string, bool>("travis.yml", false));
+            list.Add(new Tuple<string, bool>("build.js", false));
+            list.Add(new Tuple<string, bool>("editorconfig", false));
+            list.Add(new Tuple<string, bool>("gitattributes", false));
+
+            return list;
+        }
+
         public async Task<object> Get(GetDashboardPackage request)
         public async Task<object> Get(GetDashboardPackage request)
         {
         {
             var path = Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath,
             var path = Path.Combine(_serverConfigurationManager.ApplicationPaths.ProgramDataPath,
@@ -344,30 +376,12 @@ namespace MediaBrowser.WebDashboard.Api
             // Try to trim the output size a bit
             // Try to trim the output size a bit
             var bowerPath = Path.Combine(path, "bower_components");
             var bowerPath = Path.Combine(path, "bower_components");
 
 
-            if (!string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
-            {
-                //var versionedBowerPath = Path.Combine(Path.GetDirectoryName(bowerPath), "bower_components" + _appHost.ApplicationVersion);
-                //Directory.Move(bowerPath, versionedBowerPath);
-                //bowerPath = versionedBowerPath;
-            }
-
-            GetUndeployedExtensions().ForEach(i => DeleteFilesByExtension(bowerPath, i));
+            GetDeployIgnoreExtensions().ForEach(i => DeleteFilesByExtension(bowerPath, i));
 
 
             DeleteFilesByExtension(bowerPath, ".json", "strings\\");
             DeleteFilesByExtension(bowerPath, ".json", "strings\\");
-            DeleteFilesByName(bowerPath, "copying", true);
-            DeleteFilesByName(bowerPath, "license", true);
-            DeleteFilesByName(bowerPath, "license-mit", true);
-            DeleteFilesByName(bowerPath, "gitignore");
-            DeleteFilesByName(bowerPath, "npmignore");
-            DeleteFilesByName(bowerPath, "jshintrc");
-            DeleteFilesByName(bowerPath, "gruntfile");
-            DeleteFilesByName(bowerPath, "bowerrc");
-            DeleteFilesByName(bowerPath, "jscsrc");
-            DeleteFilesByName(bowerPath, "hero.svg");
-            DeleteFilesByName(bowerPath, "travis.yml");
-            DeleteFilesByName(bowerPath, "build.js");
-            DeleteFilesByName(bowerPath, "editorconfig");
-            DeleteFilesByName(bowerPath, "gitattributes");
+
+            GetDeployIgnoreFilenames().ForEach(i => DeleteFilesByName(bowerPath, i.Item1, i.Item2));
+
             DeleteFoldersByName(bowerPath, "demo");
             DeleteFoldersByName(bowerPath, "demo");
             DeleteFoldersByName(bowerPath, "test");
             DeleteFoldersByName(bowerPath, "test");
             DeleteFoldersByName(bowerPath, "guides");
             DeleteFoldersByName(bowerPath, "guides");
@@ -382,8 +396,6 @@ namespace MediaBrowser.WebDashboard.Api
             }
             }
 
 
             _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true);
             _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true);
-            //_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true);
-            //_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true);
 
 
             DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components"));
             DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components"));
 
 

+ 26 - 20
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -101,6 +101,9 @@
     <Content Include="dashboard-ui\autoorganizesmart.html">
     <Content Include="dashboard-ui\autoorganizesmart.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
+    <Content Include="dashboard-ui\camerauploadsettings.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\components\appfooter\appfooter.css">
     <Content Include="dashboard-ui\components\appfooter\appfooter.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
@@ -182,6 +185,12 @@
     <Content Include="dashboard-ui\components\tvproviders\xmltv.template.html">
     <Content Include="dashboard-ui\components\tvproviders\xmltv.template.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
+    <Content Include="dashboard-ui\css\images\throbber.gif">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="dashboard-ui\scripts\camerauploadsettings.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\userpasswordpage.js">
     <Content Include="dashboard-ui\scripts\userpasswordpage.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
@@ -191,6 +200,9 @@
     <Content Include="dashboard-ui\css\dashboard.css">
     <Content Include="dashboard-ui\css\dashboard.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
+    <Content Include="dashboard-ui\css\autoorganizetable.css">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\css\images\logo.png">
     <Content Include="dashboard-ui\css\images\logo.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
@@ -320,7 +332,7 @@
     <Content Include="dashboard-ui\scripts\homeupcoming.js">
     <Content Include="dashboard-ui\scripts\homeupcoming.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\librarydisplay.js">
+    <Content Include="dashboard-ui\dashboard\librarydisplay.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\scripts\livetvguideprovider.js">
     <Content Include="dashboard-ui\scripts\livetvguideprovider.js">
@@ -332,7 +344,7 @@
     <Content Include="dashboard-ui\scripts\livetvtunerprovider-m3u.js">
     <Content Include="dashboard-ui\scripts\livetvtunerprovider-m3u.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\livetvtunerprovider-satip.js">
+    <Content Include="dashboard-ui\dashboard\livetvtunerprovider-satip.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\scripts\localsync.js">
     <Content Include="dashboard-ui\scripts\localsync.js">
@@ -350,7 +362,7 @@
     <Content Include="dashboard-ui\scripts\mysyncsettings.js">
     <Content Include="dashboard-ui\scripts\mysyncsettings.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\autoorganizesmart.js">
+    <Content Include="dashboard-ui\dashboard\autoorganizesmart.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\scripts\searchpage.js">
     <Content Include="dashboard-ui\scripts\searchpage.js">
@@ -371,7 +383,7 @@
     <Content Include="dashboard-ui\scripts\tvlatest.js">
     <Content Include="dashboard-ui\scripts\tvlatest.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\wizardcomponents.js">
+    <Content Include="dashboard-ui\dashboard\wizardcomponents.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\scripts\wizardcontroller.js">
     <Content Include="dashboard-ui\scripts\wizardcontroller.js">
@@ -497,7 +509,7 @@
     <Content Include="dashboard-ui\photos.html">
     <Content Include="dashboard-ui\photos.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\dashboardhosting.js">
+    <Content Include="dashboard-ui\dashboard\dashboardhosting.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\scripts\forgotpassword.js">
     <Content Include="dashboard-ui\scripts\forgotpassword.js">
@@ -857,13 +869,13 @@
     <Content Include="dashboard-ui\scripts\chromecast.js">
     <Content Include="dashboard-ui\scripts\chromecast.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\cinemamodeconfiguration.js">
+    <Content Include="dashboard-ui\dashboard\cinemamodeconfiguration.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\scripts\connectlogin.js">
     <Content Include="dashboard-ui\scripts\connectlogin.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\dashboardgeneral.js">
+    <Content Include="dashboard-ui\dashboard\dashboardgeneral.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\scripts\syncactivity.js">
     <Content Include="dashboard-ui\scripts\syncactivity.js">
@@ -875,7 +887,7 @@
     <Content Include="dashboard-ui\scripts\devices.js">
     <Content Include="dashboard-ui\scripts\devices.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\devicesupload.js">
+    <Content Include="dashboard-ui\dashboard\devicesupload.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\scripts\dlnaprofile.js">
     <Content Include="dashboard-ui\scripts\dlnaprofile.js">
@@ -890,10 +902,10 @@
     <Content Include="dashboard-ui\scripts\encodingsettings.js">
     <Content Include="dashboard-ui\scripts\encodingsettings.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\autoorganizetv.js">
+    <Content Include="dashboard-ui\dashboard\autoorganizetv.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\autoorganizelog.js">
+    <Content Include="dashboard-ui\dashboard\autoorganizelog.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\scripts\externalplayer.js">
     <Content Include="dashboard-ui\scripts\externalplayer.js">
@@ -989,12 +1001,6 @@
     <Content Include="dashboard-ui\scripts\livetvsuggested.js">
     <Content Include="dashboard-ui\scripts\livetvsuggested.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\sync.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-    <Content Include="dashboard-ui\scripts\thememediaplayer.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\scripts\tvupcoming.js">
     <Content Include="dashboard-ui\scripts\tvupcoming.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
@@ -1086,7 +1092,7 @@
     <Content Include="dashboard-ui\scripts\edititemmetadata.js">
     <Content Include="dashboard-ui\scripts\edititemmetadata.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\librarysettings.js">
+    <Content Include="dashboard-ui\dashboard\librarysettings.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\scripts\musicrecommended.js">
     <Content Include="dashboard-ui\scripts\musicrecommended.js">
@@ -1353,7 +1359,7 @@
     </Content>
     </Content>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
-    <Content Include="dashboard-ui\scripts\logpage.js">
+    <Content Include="dashboard-ui\dashboard\logpage.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
   </ItemGroup>
   </ItemGroup>
@@ -1426,7 +1432,7 @@
     </Content>
     </Content>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
-    <Content Include="dashboard-ui\scripts\aboutpage.js">
+    <Content Include="dashboard-ui\dashboard\aboutpage.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\css\images\supporter\supporterflag.png">
     <Content Include="dashboard-ui\css\images\supporter\supporterflag.png">
@@ -1438,7 +1444,7 @@
     <Content Include="dashboard-ui\itemlist.html">
     <Content Include="dashboard-ui\itemlist.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\scripts\wizardfinishpage.js">
+    <Content Include="dashboard-ui\dashboard\wizardfinishpage.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
     <Content Include="dashboard-ui\css\images\items\detail\video.png">
     <Content Include="dashboard-ui\css\images\items\detail\video.png">

+ 9 - 0
MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs

@@ -827,6 +827,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers
                         }
                         }
                         break;
                         break;
                     }
                     }
+                case "tvmazeid":
+                    {
+                        var id = reader.ReadElementContentAsString();
+                        if (!string.IsNullOrWhiteSpace(id))
+                        {
+                            item.SetProviderId(MetadataProviders.TvMaze, id);
+                        }
+                        break;
+                    }
                 case "audiodbartistid":
                 case "audiodbartistid":
                     {
                     {
                         var id = reader.ReadElementContentAsString();
                         var id = reader.ReadElementContentAsString();

+ 6 - 0
MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

@@ -822,6 +822,12 @@ namespace MediaBrowser.XbmcMetadata.Savers
                 writer.WriteElementString("tvrageid", externalId);
                 writer.WriteElementString("tvrageid", externalId);
             }
             }
 
 
+            externalId = item.GetProviderId(MetadataProviders.TvMaze);
+            if (!string.IsNullOrEmpty(externalId))
+            {
+                writer.WriteElementString("tvmazeid", externalId);
+            }
+
             if (options.SaveImagePathsInNfo)
             if (options.SaveImagePathsInNfo)
             {
             {
                 AddImages(item, writer, libraryManager, config);
                 AddImages(item, writer, libraryManager, config);

+ 3 - 1
MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs

@@ -118,7 +118,9 @@ namespace MediaBrowser.XbmcMetadata.Savers
                     "airsbefore_season",
                     "airsbefore_season",
                     "DVD_episodenumber",
                     "DVD_episodenumber",
                     "DVD_season",
                     "DVD_season",
-                    "absolute_number"
+                    "absolute_number",
+                    "displayseason",
+                    "displayepisode"
             };
             };
 
 
             return list;
             return list;

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.654</version>
+        <version>3.0.655</version>
         <title>MediaBrowser.Common.Internal</title>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
         <description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Emby 2013</copyright>
         <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.654" />
+            <dependency id="MediaBrowser.Common" version="3.0.655" />
             <dependency id="NLog" version="4.3.6" />
             <dependency id="NLog" version="4.3.6" />
             <dependency id="SimpleInjector" version="3.2.0" />
             <dependency id="SimpleInjector" version="3.2.0" />
         </dependencies>
         </dependencies>

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Common</id>
         <id>MediaBrowser.Common</id>
-        <version>3.0.654</version>
+        <version>3.0.655</version>
         <title>MediaBrowser.Common</title>
         <title>MediaBrowser.Common</title>
         <authors>Emby Team</authors>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Server.Core</id>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.654</version>
+        <version>3.0.655</version>
         <title>Media Browser.Server.Core</title>
         <title>Media Browser.Server.Core</title>
         <authors>Emby Team</authors>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Emby Server.</description>
         <description>Contains core components required to build plugins for Emby Server.</description>
         <copyright>Copyright © Emby 2013</copyright>
         <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.654" />
+            <dependency id="MediaBrowser.Common" version="3.0.655" />
 			<dependency id="Interfaces.IO" version="1.0.0.5" />
 			<dependency id="Interfaces.IO" version="1.0.0.5" />
         </dependencies>
         </dependencies>
     </metadata>
     </metadata>