瀏覽代碼

Merge pull request #2630 from MediaBrowser/dev

Dev
Luke 8 年之前
父節點
當前提交
275fe66220
共有 31 個文件被更改,包括 353 次插入518 次删除
  1. 44 14
      Emby.Common.Implementations/IO/ManagedFileSystem.cs
  2. 20 5
      Emby.Common.Implementations/IO/SharpCifsFileSystem.cs
  3. 35 9
      Emby.Drawing.Skia/StripCollageBuilder.cs
  4. 31 18
      Emby.Drawing/ImageProcessor.cs
  5. 5 0
      Emby.Drawing/NullImageEncoder.cs
  6. 1 1
      Emby.Server.Core/IO/LibraryMonitor.cs
  7. 9 1
      Emby.Server.Implementations/IO/FileRefresher.cs
  8. 0 4
      MediaBrowser.Api/MediaBrowser.Api.csproj
  9. 4 8
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  10. 2 0
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  11. 2 1
      MediaBrowser.Api/Reports/Common/HeaderMetadata.cs
  12. 5 0
      MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs
  13. 10 3
      MediaBrowser.Api/Reports/Data/ReportBuilder.cs
  14. 0 256
      MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs
  15. 0 33
      MediaBrowser.Api/Reports/Stat/ReportStatGroup.cs
  16. 0 23
      MediaBrowser.Api/Reports/Stat/ReportStatItem.cs
  17. 0 24
      MediaBrowser.Api/Reports/Stat/ReportStatResult.cs
  18. 2 0
      MediaBrowser.Controller/Drawing/IImageEncoder.cs
  19. 34 3
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  20. 42 4
      MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
  21. 1 5
      MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
  22. 2 1
      MediaBrowser.Model/IO/IFileSystem.cs
  23. 6 21
      MediaBrowser.Providers/Manager/ImageSaver.cs
  24. 10 9
      MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
  25. 11 6
      MediaBrowser.Providers/Omdb/OmdbProvider.cs
  26. 2 2
      MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
  27. 2 2
      MediaBrowser.ServerApplication/ImageEncoderHelper.cs
  28. 71 37
      MediaBrowser.ServerApplication/MainStartup.cs
  29. 0 21
      MediaBrowser.ServerApplication/Native/LoopUtil.cs
  30. 1 6
      MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
  31. 1 1
      SharedVersion.cs

+ 44 - 14
Emby.Common.Implementations/IO/ManagedFileSystem.cs

@@ -518,6 +518,49 @@ namespace Emby.Common.Implementations.IO
             }
         }
 
+        public void SetAttributes(string path, bool isHidden, bool isReadOnly)
+        {
+            if (_sharpCifsFileSystem.IsEnabledForPath(path))
+            {
+                _sharpCifsFileSystem.SetAttributes(path, isHidden, isReadOnly);
+                return;
+            }
+
+            var info = GetFileInfo(path);
+
+            if (!info.Exists)
+            {
+                return;
+            }
+
+            if (info.IsReadOnly == isReadOnly && info.IsHidden == isHidden)
+            {
+                return;
+            }
+
+            var attributes = File.GetAttributes(path);
+
+            if (isReadOnly)
+            {
+                attributes = attributes | FileAttributes.ReadOnly;
+            }
+            else
+            {
+                attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
+            }
+
+            if (isHidden)
+            {
+                attributes = attributes | FileAttributes.Hidden;
+            }
+            else
+            {
+                attributes = RemoveAttribute(attributes, FileAttributes.Hidden);
+            }
+
+            File.SetAttributes(path, attributes);
+        }
+
         private static FileAttributes RemoveAttribute(FileAttributes attributes, FileAttributes attributesToRemove)
         {
             return attributes & ~attributesToRemove;
@@ -690,20 +733,7 @@ namespace Emby.Common.Implementations.IO
                 return;
             }
 
-            var fileInfo = GetFileInfo(path);
-
-            if (fileInfo.Exists)
-            {
-                if (fileInfo.IsHidden)
-                {
-                    SetHidden(path, false);
-                }
-                if (fileInfo.IsReadOnly)
-                {
-                    SetReadOnly(path, false);
-                }
-            }
-
+            SetAttributes(path, false, false);
             File.Delete(path);
         }
 

+ 20 - 5
Emby.Common.Implementations/IO/SharpCifsFileSystem.cs

@@ -166,23 +166,38 @@ namespace Emby.Common.Implementations.IO
         public void SetHidden(string path, bool isHidden)
         {
             var file = CreateSmbFile(path);
+            SetHidden(file, isHidden);
+        }
+
+        public void SetReadOnly(string path, bool isReadOnly)
+        {
+            var file = CreateSmbFile(path);
+            SetReadOnly(file, isReadOnly);
+        }
+
+        public void SetAttributes(string path, bool isHidden, bool isReadOnly)
+        {
+            var file = CreateSmbFile(path);
+            SetHidden(file, isHidden);
+            SetReadOnly(file, isReadOnly);
+        }
 
+        private void SetHidden(SmbFile file, bool isHidden)
+        {
             var isCurrentlyHidden = file.IsHidden();
 
             if (isCurrentlyHidden && !isHidden)
             {
-                file.SetAttributes(file.GetAttributes() & ~SmbFile.AttrReadonly);
+                file.SetAttributes(file.GetAttributes() & ~SmbFile.AttrHidden);
             }
             else if (!isCurrentlyHidden && isHidden)
             {
-                file.SetAttributes(file.GetAttributes() | SmbFile.AttrReadonly);
+                file.SetAttributes(file.GetAttributes() | SmbFile.AttrHidden);
             }
         }
 
-        public void SetReadOnly(string path, bool isReadOnly)
+        private void SetReadOnly(SmbFile file, bool isReadOnly)
         {
-            var file = CreateSmbFile(path);
-
             var isCurrentlyReadOnly = !file.CanWrite();
 
             if (isCurrentlyReadOnly && !isReadOnly)

+ 35 - 9
Emby.Drawing.Skia/StripCollageBuilder.cs

@@ -72,7 +72,8 @@ namespace Emby.Drawing.Skia
             {
                 canvas.Clear(SKColors.Black);
 
-                var iSlice = Convert.ToInt32(width * 0.24125);
+                // determine sizes for each image that will composited into the final image
+                var iSlice = Convert.ToInt32(width * 0.23475);
                 int iTrans = Convert.ToInt32(height * .25);
                 int iHeight = Convert.ToInt32(height * .70);
                 var horizontalImagePadding = Convert.ToInt32(width * 0.0125);
@@ -83,29 +84,54 @@ namespace Emby.Drawing.Skia
                 {
                     using (var currentBitmap = SKBitmap.Decode(paths[imageIndex]))
                     {
+                        // resize to the same aspect as the original
                         int iWidth = (int)Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height);
                         using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType))
                         {
                             currentBitmap.Resize(resizeBitmap, SKBitmapResizeMethod.Lanczos3);
+                            // determine how much to crop
                             int ix = (int)Math.Abs((iWidth - iSlice) / 2);
                             using (var image = SKImage.FromBitmap(resizeBitmap))
                             {
+                                // crop image
                                 using (var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)))
                                 {
-                                    canvas.DrawImage(subset, (horizontalImagePadding * (i + 1)) + (iSlice * i), 0);
+                                    // draw image onto canvas
+                                    canvas.DrawImage(subset, (horizontalImagePadding * (i + 1)) + (iSlice * i), verticalSpacing);
 
                                     using (var croppedBitmap = SKBitmap.FromImage(subset))
                                     {
-                                        using (var flipped = new SKBitmap(croppedBitmap.Width, croppedBitmap.Height / 2, croppedBitmap.ColorType, croppedBitmap.AlphaType))
+                                        // create reflection of image below the drawn image
+                                        using (var reflectionBitmap = new SKBitmap(croppedBitmap.Width, croppedBitmap.Height / 2, croppedBitmap.ColorType, croppedBitmap.AlphaType))
                                         {
-                                            croppedBitmap.Resize(flipped, SKBitmapResizeMethod.Lanczos3);
+                                            // resize to half height
+                                            croppedBitmap.Resize(reflectionBitmap, SKBitmapResizeMethod.Lanczos3);
 
-                                            using (var gradient = new SKPaint())
+                                            using (var flippedBitmap = new SKBitmap(reflectionBitmap.Width, reflectionBitmap.Height, reflectionBitmap.ColorType, reflectionBitmap.AlphaType))
                                             {
-                                                var matrix = SKMatrix.MakeScale(1, -1);
-                                                matrix.SetScaleTranslate(1, -1, 0, flipped.Height);
-                                                gradient.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(0, flipped.Height), new[] { new SKColor(0, 0, 0, 0), SKColors.Black }, null, SKShaderTileMode.Clamp, matrix);
-                                                canvas.DrawBitmap(flipped, (horizontalImagePadding * (i + 1)) + (iSlice * i), iHeight + verticalSpacing, gradient);
+                                                using (var flippedCanvas = new SKCanvas(flippedBitmap))
+                                                {
+                                                    // flip image vertically
+                                                    var matrix = SKMatrix.MakeScale(1, -1);
+                                                    matrix.SetScaleTranslate(1, -1, 0, flippedBitmap.Height);
+                                                    flippedCanvas.SetMatrix(matrix);
+                                                    flippedCanvas.DrawBitmap(reflectionBitmap, 0, 0);
+                                                    flippedCanvas.ResetMatrix();
+
+                                                    // create gradient to make image appear as a reflection
+                                                    var remainingHeight = height - (iHeight + (2 * verticalSpacing));
+                                                    flippedCanvas.ClipRect(SKRect.Create(reflectionBitmap.Width, remainingHeight));
+                                                    using (var gradient = new SKPaint())
+                                                    {
+                                                        gradient.IsAntialias = true;
+                                                        gradient.BlendMode = SKBlendMode.SrcOver;
+                                                        gradient.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(0, remainingHeight), new[] { new SKColor(0, 0, 0, 128), new SKColor(0, 0, 0, 208), new SKColor(0, 0, 0, 240), new SKColor(0, 0, 0, 255) }, null, SKShaderTileMode.Clamp);
+                                                        flippedCanvas.DrawPaint(gradient);
+                                                    }
+
+                                                    // finally draw reflection onto canvas
+                                                    canvas.DrawBitmap(flippedBitmap, (horizontalImagePadding * (i + 1)) + (iSlice * i), iHeight + (2 * verticalSpacing));
+                                                }
                                             }
                                         }
                                     }

+ 31 - 18
Emby.Drawing/ImageProcessor.cs

@@ -198,7 +198,7 @@ namespace Emby.Drawing
                 return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
             }
 
-            ImageSize? originalImageSize;
+            ImageSize? originalImageSize = null;
             try
             {
                 originalImageSize = GetImageSize(originalImagePath, dateModified, true);
@@ -333,7 +333,7 @@ namespace Emby.Drawing
                 return new ImageSize(options.Width.Value, options.Height.Value);
             }
 
-            var aspect = GetEstimatedAspectRatio(options.Image.Type);
+            var aspect = GetEstimatedAspectRatio(options.Image.Type, options.Item);
 
             var width = options.Width ?? options.MaxWidth;
 
@@ -348,7 +348,7 @@ namespace Emby.Drawing
             return new ImageSize(widthValue, height);
         }
 
-        private double GetEstimatedAspectRatio(ImageType type)
+        private double GetEstimatedAspectRatio(ImageType type, IHasImages item)
         {
             switch (type)
             {
@@ -368,7 +368,7 @@ namespace Emby.Drawing
                 case ImageType.Logo:
                     return 2.58;
                 case ImageType.Primary:
-                    return .667;
+                    return item.GetDefaultPrimaryImageAspectRatio() ?? .667;
                 default:
                     return 1;
             }
@@ -499,26 +499,39 @@ namespace Emby.Drawing
         /// <returns>ImageSize.</returns>
         private ImageSize GetImageSizeInternal(string path, bool allowSlowMethod)
         {
+            // Can't use taglib because it keeps a lock on the file
+            //try
+            //{
+            //    using (var file = TagLib.File.Create(new StreamFileAbstraction(Path.GetFileName(path), _fileSystem.OpenRead(path), null)))
+            //    {
+            //        var image = file as TagLib.Image.File;
+
+            //        var properties = image.Properties;
+
+            //        return new ImageSize
+            //        {
+            //            Height = properties.PhotoHeight,
+            //            Width = properties.PhotoWidth
+            //        };
+            //    }
+            //}
+            //catch
+            //{
+            //}
+
             try
             {
-                using (var file = TagLib.File.Create(new StreamFileAbstraction(Path.GetFileName(path), _fileSystem.OpenRead(path), null)))
-                {
-                    var image = file as TagLib.Image.File;
-
-                    var properties = image.Properties;
-
-                    return new ImageSize
-                    {
-                        Height = properties.PhotoHeight,
-                        Width = properties.PhotoWidth
-                    };
-                }
+                return ImageHeader.GetDimensions(path, _logger, _fileSystem);
             }
             catch
             {
-            }
+                if (allowSlowMethod)
+                {
+                    return _imageEncoder.GetImageSize(path);
+                }
 
-            return ImageHeader.GetDimensions(path, _logger, _fileSystem);
+                throw;
+            }
         }
 
         private readonly ITimer _saveImageSizeTimer;

+ 5 - 0
Emby.Drawing/NullImageEncoder.cs

@@ -57,6 +57,11 @@ namespace Emby.Drawing
             get { return false; }
         }
 
+        public ImageSize GetImageSize(string path)
+        {
+            throw new NotImplementedException();
+        }
+
         public void Dispose()
         {
         }

+ 1 - 1
Emby.Server.Core/IO/LibraryMonitor.cs

@@ -537,7 +537,7 @@ namespace Emby.Server.Core.IO
                     }
                 }
 
-                var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory, _environmentInfo);
+                var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory, _environmentInfo, LibraryManager);
                 newRefresher.Completed += NewRefresher_Completed;
                 _activeRefreshers.Add(newRefresher);
             }

+ 9 - 1
Emby.Server.Implementations/IO/FileRefresher.cs

@@ -34,8 +34,9 @@ namespace Emby.Server.Implementations.IO
 
         public event EventHandler<EventArgs> Completed;
         private readonly IEnvironmentInfo _environmentInfo;
+        private readonly ILibraryManager _libraryManager;
 
-        public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo)
+        public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1)
         {
             logger.Debug("New file refresher created for {0}", path);
             Path = path;
@@ -47,6 +48,7 @@ namespace Emby.Server.Implementations.IO
             Logger = logger;
             _timerFactory = timerFactory;
             _environmentInfo = environmentInfo;
+            _libraryManager = libraryManager1;
             AddPath(path);
         }
 
@@ -235,6 +237,12 @@ namespace Emby.Server.Implementations.IO
                 return false;
             }
 
+            // Only try to open video files
+            if (!_libraryManager.IsVideoFile(path))
+            {
+                return false;
+            }
+
             try
             {
                 var data = _fileSystem.GetFileSystemInfo(path);

+ 0 - 4
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -82,10 +82,6 @@
     <Compile Include="Reports\Model\ReportRow.cs" />
     <Compile Include="Reports\ReportRequests.cs" />
     <Compile Include="Reports\ReportsService.cs" />
-    <Compile Include="Reports\Stat\ReportStatBuilder.cs" />
-    <Compile Include="Reports\Stat\ReportStatGroup.cs" />
-    <Compile Include="Reports\Stat\ReportStatItem.cs" />
-    <Compile Include="Reports\Stat\ReportStatResult.cs" />
     <Compile Include="Social\SharingService.cs" />
     <Compile Include="StartupWizardService.cs" />
     <Compile Include="Subtitles\SubtitleService.cs" />

+ 4 - 8
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -879,7 +879,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 // Add resolution params, if specified
                 if (!hasGraphicalSubs)
                 {
-                    args += EncodingHelper.GetOutputSizeParam(state, codec, EnableCopyTs(state));
+                    args += EncodingHelper.GetOutputSizeParam(state, codec, true);
                 }
 
                 // This is for internal graphical subs
@@ -891,7 +891,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 //args += " -flags -global_header";
             }
 
-            if (EnableCopyTs(state) && args.IndexOf("-copyts", StringComparison.OrdinalIgnoreCase) == -1)
+            if (args.IndexOf("-copyts", StringComparison.OrdinalIgnoreCase) == -1)
             {
                 args += " -copyts";
             }
@@ -901,13 +901,9 @@ namespace MediaBrowser.Api.Playback.Hls
                 args += " -vsync " + state.OutputVideoSync;
             }
 
-            return args;
-        }
+            args += EncodingHelper.GetOutputFFlags(state);
 
-        private bool EnableCopyTs(StreamState state)
-        {
-            //return state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
-            return true;
+            return args;
         }
 
         protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)

+ 2 - 0
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -124,6 +124,8 @@ namespace MediaBrowser.Api.Playback.Hls
                 args += " -vsync " + state.OutputVideoSync;
             }
 
+            args += EncodingHelper.GetOutputFFlags(state);
+
             return args;
         }
 

+ 2 - 1
MediaBrowser.Api/Reports/Common/HeaderMetadata.cs

@@ -35,7 +35,8 @@ namespace MediaBrowser.Api.Reports
 		Tracks,
 		EpisodeSeries,
 		EpisodeSeason,
-		AudioAlbumArtist,
+        EpisodeNumber,
+        AudioAlbumArtist,
 		MusicArtist,
 		AudioAlbum,
         Locked,

+ 5 - 0
MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs

@@ -148,6 +148,11 @@ namespace MediaBrowser.Api.Reports
         /// <returns> The localized header. </returns>
         protected static string GetLocalizedHeader(HeaderMetadata internalHeader)
         {
+            if (internalHeader == HeaderMetadata.EpisodeNumber)
+            {
+                return "Episode";
+            }
+
             string headerName = "";
             if (internalHeader != HeaderMetadata.None)
             {

+ 10 - 3
MediaBrowser.Api/Reports/Data/ReportBuilder.cs

@@ -102,7 +102,7 @@ namespace MediaBrowser.Api.Reports
 						HeaderMetadata.Series,
 						HeaderMetadata.Season,
 						HeaderMetadata.SeasonNumber,
-						HeaderMetadata.DateAdded,
+                        HeaderMetadata.DateAdded,
 						HeaderMetadata.Year,
 						HeaderMetadata.Genres
 					};
@@ -269,10 +269,11 @@ namespace MediaBrowser.Api.Reports
                         HeaderMetadata.ImagePrimary,
                         HeaderMetadata.ImageBackdrop,
                         HeaderMetadata.ImageLogo,
-						HeaderMetadata.Name,
+                        HeaderMetadata.Name,
 						HeaderMetadata.EpisodeSeries,
 						HeaderMetadata.Season,
-						HeaderMetadata.DateAdded,
+                        HeaderMetadata.EpisodeNumber,
+                        HeaderMetadata.DateAdded,
 						HeaderMetadata.ReleaseDate,
 						HeaderMetadata.Year,
 						HeaderMetadata.Genres,
@@ -450,6 +451,12 @@ namespace MediaBrowser.Api.Reports
                     internalHeader = HeaderMetadata.Season;
                     break;
 
+                case HeaderMetadata.EpisodeNumber:
+                    option.Column = (i, r) => this.GetObject<BaseItem, string>(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber.ToString());
+                    //option.Header.SortField = "IndexNumber";
+                    //option.Header.HeaderFieldType = ReportFieldType.Int;
+                    break;
+
                 case HeaderMetadata.Network:
                     option.Column = (i, r) => this.GetListAsString(i.Studios);
                     option.ItemID = (i) => this.GetStudioID(i.Studios.FirstOrDefault());

+ 0 - 256
MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs

@@ -1,256 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace MediaBrowser.Api.Reports
-{
-    /// <summary> A report stat builder. </summary>
-    /// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/>
-    public class ReportStatBuilder : ReportBuilderBase
-    {
-        #region [Constructors]
-
-        /// <summary>
-        /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatBuilder class. </summary>
-        /// <param name="libraryManager"> Manager for library. </param>
-        public ReportStatBuilder(ILibraryManager libraryManager)
-            : base(libraryManager)
-        {
-        }
-
-        #endregion
-
-        #region [Public Methods]
-
-        /// <summary> Gets report stat result. </summary>
-        /// <param name="items"> The items. </param>
-        /// <param name="reportIncludeItemTypes"> List of types of the report include items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The report stat result. </returns>
-        public ReportStatResult GetResult(BaseItem[] items, ReportIncludeItemTypes reportIncludeItemTypes, int topItem = 5)
-        {
-            ReportStatResult result = new ReportStatResult();
-            result = this.GetResultGenres(result, items, topItem);
-            result = this.GetResultStudios(result, items, topItem);
-            result = this.GetResultPersons(result, items, topItem);
-            result = this.GetResultProductionYears(result, items, topItem);
-            result = this.GetResultCommunityRatings(result, items, topItem);
-            result = this.GetResultParentalRatings(result, items, topItem);
-
-            switch (reportIncludeItemTypes)
-            {
-                case ReportIncludeItemTypes.Season:
-                case ReportIncludeItemTypes.Series:
-                case ReportIncludeItemTypes.MusicAlbum:
-                case ReportIncludeItemTypes.MusicArtist:
-                case ReportIncludeItemTypes.Game:
-                    break;
-                case ReportIncludeItemTypes.Movie:
-                case ReportIncludeItemTypes.BoxSet:
-
-                    break;
-                case ReportIncludeItemTypes.Book:
-                case ReportIncludeItemTypes.Episode:
-                case ReportIncludeItemTypes.Video:
-                case ReportIncludeItemTypes.MusicVideo:
-                case ReportIncludeItemTypes.Trailer:
-                case ReportIncludeItemTypes.Audio:
-                case ReportIncludeItemTypes.BaseItem:
-                default:
-                    break;
-            }
-
-            result.Groups = result.Groups.OrderByDescending(n => n.Items.Count()).ToList();
-
-            return result;
-        }
-
-        #endregion
-
-        #region [Protected Internal Methods]
-        /// <summary> Gets the headers. </summary>
-        /// <typeparam name="H"> Type of the header. </typeparam>
-        /// <param name="request"> The request. </param>
-        /// <returns> The headers. </returns>
-        /// <seealso cref="M:MediaBrowser.Api.Reports.ReportBuilderBase.GetHeaders{H}(H)"/>
-        protected internal override List<ReportHeader> GetHeaders<H>(H request)
-        {
-            throw new NotImplementedException();
-        }
-
-        #endregion
-
-        #region [Private Methods]
-
-        /// <summary> Gets the groups. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="header"> The header. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <param name="top"> The top. </param>
-        private void GetGroups(ReportStatResult result, string header, int topItem, IEnumerable<ReportStatItem> top)
-        {
-            if (top != null && top.Count() > 0)
-            {
-                var group = new ReportStatGroup { Header = ReportStatGroup.FormatedHeader(header, topItem) };
-                group.Items.AddRange(top);
-                result.Groups.Add(group);
-            }
-        }
-
-        /// <summary> Gets result community ratings. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result community ratings. </returns>
-        private ReportStatResult GetResultCommunityRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.CommunityRating), topItem,
-                       items.Where(x => x.CommunityRating != null && x.CommunityRating > 0)
-                           .GroupBy(x => x.CommunityRating)
-                           .OrderByDescending(x => x.Count())
-                           .Take(topItem)
-                           .Select(x => new ReportStatItem
-                           {
-                               Name = x.Key.ToString(),
-                               Value = x.Count().ToString()
-                           })
-               );
-
-            return result;
-        }
-
-        /// <summary> Gets result genres. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result genres. </returns>
-        private ReportStatResult GetResultGenres(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Genres), topItem,
-                            items.SelectMany(x => x.Genres)
-                                .GroupBy(x => x)
-                                .OrderByDescending(x => x.Count())
-                                .Take(topItem)
-                                .Select(x => new ReportStatItem
-                                {
-                                    Name = x.Key,
-                                    Value = x.Count().ToString(),
-                                    Id = GetGenreID(x.Key)
-                                }));
-            return result;
-
-        }
-
-        /// <summary> Gets result parental ratings. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result parental ratings. </returns>
-        private ReportStatResult GetResultParentalRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.ParentalRatings), topItem,
-                       items.Where(x => x.OfficialRating != null)
-                           .GroupBy(x => x.OfficialRating)
-                           .OrderByDescending(x => x.Count())
-                           .Take(topItem)
-                           .Select(x => new ReportStatItem
-                           {
-                               Name = x.Key.ToString(),
-                               Value = x.Count().ToString()
-                           })
-               );
-
-            return result;
-        }
-
-        /// <summary> Gets result persons. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result persons. </returns>
-        private ReportStatResult GetResultPersons(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            List<HeaderMetadata> t = new List<HeaderMetadata> 
-            { 
-                HeaderMetadata.Actor, 
-                HeaderMetadata.Composer, 
-                HeaderMetadata.Director, 
-                HeaderMetadata.GuestStar, 
-                HeaderMetadata.Producer,
-                HeaderMetadata.Writer, 
-                HeaderMetadata.Artist, 
-                HeaderMetadata.AlbumArtist
-            };
-            foreach (var item in t)
-            {
-                var ps = items.SelectMany(x => _libraryManager.GetPeople(x))
-                                .Where(n => n.Type == item.ToString())
-                                .GroupBy(x => x.Name)
-                                .OrderByDescending(x => x.Count())
-                                .Take(topItem);
-                if (ps != null && ps.Count() > 0)
-                    this.GetGroups(result, GetLocalizedHeader(item), topItem,
-                            ps.Select(x => new ReportStatItem
-                                    {
-                                        Name = x.Key,
-                                        Value = x.Count().ToString(),
-                                        Id = GetPersonID(x.Key)
-                                    })
-                    );
-            }
-
-            return result;
-        }
-
-        /// <summary> Gets result production years. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result production years. </returns>
-        private ReportStatResult GetResultProductionYears(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Year), topItem,
-                    items.Where(x => x.ProductionYear != null && x.ProductionYear > 0)
-                        .GroupBy(x => x.ProductionYear)
-                        .OrderByDescending(x => x.Count())
-                        .Take(topItem)
-                        .Select(x => new ReportStatItem
-                        {
-                            Name = x.Key.ToString(),
-                            Value = x.Count().ToString()
-                        })
-            );
-
-            return result;
-        }
-
-        /// <summary> Gets result studios. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result studios. </returns>
-        private ReportStatResult GetResultStudios(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Studios), topItem,
-                                    items.SelectMany(x => x.Studios)
-                                        .GroupBy(x => x)
-                                        .OrderByDescending(x => x.Count())
-                                        .Take(topItem)
-                                        .Select(x => new ReportStatItem
-                                        {
-                                            Name = x.Key,
-                                            Value = x.Count().ToString(),
-                                            Id = GetStudioID(x.Key)
-                                        })
-                    );
-
-            return result;
-
-        }
-
-        #endregion
-
-    }
-}

+ 0 - 33
MediaBrowser.Api/Reports/Stat/ReportStatGroup.cs

@@ -1,33 +0,0 @@
-using System.Collections.Generic;
-
-namespace MediaBrowser.Api.Reports
-{
-	/// <summary> A report stat group. </summary>
-	public class ReportStatGroup
-	{
-		/// <summary>
-		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatGroup class. </summary>
-		public ReportStatGroup()
-		{
-			Items = new List<ReportStatItem>();
-			TotalRecordCount = 0;
-		}
-
-		/// <summary> Gets or sets the header. </summary>
-		/// <value> The header. </value>
-		public string Header { get; set; }
-
-		/// <summary> Gets or sets the items. </summary>
-		/// <value> The items. </value>
-		public List<ReportStatItem> Items { get; set; }
-
-		/// <summary> Gets or sets the number of total records. </summary>
-		/// <value> The total number of record count. </value>
-		public int TotalRecordCount { get; set; }
-
-		internal static string FormatedHeader(string header, int topItem)
-		{
-			return string.Format("Top {0} {1}", topItem, header);
-		}
-	}
-}

+ 0 - 23
MediaBrowser.Api/Reports/Stat/ReportStatItem.cs

@@ -1,23 +0,0 @@
-namespace MediaBrowser.Api.Reports
-{
-	/// <summary> A report stat item. </summary>
-	public class ReportStatItem
-	{
-		/// <summary> Gets or sets the name. </summary>
-		/// <value> The name. </value>
-		public string Name { get; set; }
-
-		/// <summary> Gets or sets the image. </summary>
-		/// <value> The image. </value>
-		public string Image { get; set; }
-
-		/// <summary> Gets or sets the value. </summary>
-		/// <value> The value. </value>
-		public string Value { get; set; }
-
-		/// <summary> Gets or sets the identifier. </summary>
-		/// <value> The identifier. </value>
-		public string Id { get; set; }
-
-	}
-}

+ 0 - 24
MediaBrowser.Api/Reports/Stat/ReportStatResult.cs

@@ -1,24 +0,0 @@
-using System.Collections.Generic;
-
-namespace MediaBrowser.Api.Reports
-{
-	/// <summary> Encapsulates the result of a report stat. </summary>
-	public class ReportStatResult 
-	{
-		/// <summary>
-		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatResult class. </summary>
-		public ReportStatResult()
-		{
-			Groups = new List<ReportStatGroup>();
-			TotalRecordCount = 0;
-		}
-
-		/// <summary> Gets or sets the groups. </summary>
-		/// <value> The groups. </value>
-		public List<ReportStatGroup> Groups { get; set; }
-
-		/// <summary> Gets or sets the number of total records. </summary>
-		/// <value> The total number of record count. </value>
-		public int TotalRecordCount { get; set; }	
-	}
-}

+ 2 - 0
MediaBrowser.Controller/Drawing/IImageEncoder.cs

@@ -50,5 +50,7 @@ namespace MediaBrowser.Controller.Drawing
         /// </summary>
         /// <value><c>true</c> if [supports image encoding]; otherwise, <c>false</c>.</value>
         bool SupportsImageEncoding { get; }
+
+        ImageSize GetImageSize(string path);
     }
 }

+ 34 - 3
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -1306,7 +1306,8 @@ namespace MediaBrowser.Controller.MediaEncoding
                 filters.Add("format=nv12|vaapi");
                 filters.Add("hwupload");
             }
-            else if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+
+            if (state.DeInterlace && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
             {
                 filters.Add("yadif=0:-1:0");
             }
@@ -1533,14 +1534,26 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
 
             var flags = new List<string>();
-            if (state.IgnoreDts)
+            if (state.IgnoreInputDts)
             {
                 flags.Add("+igndts");
             }
-            if (state.IgnoreIndex)
+            if (state.IgnoreInputIndex)
             {
                 flags.Add("+ignidx");
             }
+            if (state.GenPtsInput)
+            {
+                flags.Add("+genpts");
+            }
+            if (state.DiscardCorruptFramesInput)
+            {
+                flags.Add("+discardcorrupt");
+            }
+            if (state.EnableFastSeekInput)
+            {
+                flags.Add("+fastseek");
+            }
 
             if (flags.Count > 0)
             {
@@ -1864,6 +1877,22 @@ namespace MediaBrowser.Controller.MediaEncoding
                 ).Trim();
         }
 
+        public string GetOutputFFlags(EncodingJobInfo state)
+        {
+            var flags = new List<string>();
+            if (state.GenPtsOutput)
+            {
+                flags.Add("+genpts");
+            }
+
+            if (flags.Count > 0)
+            {
+                return " -fflags " + string.Join("", flags.ToArray());
+            }
+
+            return string.Empty;
+        }
+
         public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultH264Preset)
         {
             var args = "-codec:v:0 " + videoCodec;
@@ -1943,6 +1972,8 @@ namespace MediaBrowser.Controller.MediaEncoding
                 args += " -vsync " + state.OutputVideoSync;
             }
 
+            args += GetOutputFFlags(state);
+
             return args;
         }
 

+ 42 - 4
MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs

@@ -39,14 +39,52 @@ namespace MediaBrowser.Controller.MediaEncoding
 
         public bool ReadInputAtNativeFramerate { get; set; }
 
-        public bool IgnoreDts
+        public bool IgnoreInputDts
         {
-            get { return MediaSource.IgnoreDts; }
+            get
+            {
+                return MediaSource.IgnoreDts;
+            }
+        }
+
+        public bool IgnoreInputIndex
+        {
+            get
+            {
+                return MediaSource.IgnoreIndex;
+            }
+        }
+
+        public bool GenPtsInput
+        {
+            get
+            {
+                return false;
+            }
         }
 
-        public bool IgnoreIndex
+        public bool DiscardCorruptFramesInput
         {
-            get { return MediaSource.IgnoreIndex; }
+            get
+            {
+                return false;
+            }
+        }
+
+        public bool EnableFastSeekInput
+        {
+            get
+            {
+                return false;
+            }
+        }
+
+        public bool GenPtsOutput
+        {
+            get
+            {
+                return false;
+            }
         }
 
         public string OutputContainer { get; set; }

+ 1 - 5
MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs

@@ -218,13 +218,9 @@ namespace MediaBrowser.LocalMetadata.Savers
             {
                 if (file.IsHidden)
                 {
-                    FileSystem.SetHidden(path, false);
                     wasHidden = true;
                 }
-                if (file.IsReadOnly)
-                {
-                    FileSystem.SetReadOnly(path, false);
-                }
+                FileSystem.SetAttributes(path, false, false);
             }
 
             using (var filestream = FileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))

+ 2 - 1
MediaBrowser.Model/IO/IFileSystem.cs

@@ -313,7 +313,8 @@ namespace MediaBrowser.Model.IO
         IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false);
 
         void SetHidden(string path, bool isHidden);
-        void SetReadOnly(string path, bool isHidden);
+        void SetReadOnly(string path, bool readOnly);
+        void SetAttributes(string path, bool isHidden, bool readOnly);
 
         char DirectorySeparatorChar { get; }
 

+ 6 - 21
MediaBrowser.Providers/Manager/ImageSaver.cs

@@ -166,7 +166,7 @@ namespace MediaBrowser.Providers.Manager
             {
                 var currentPath = currentImagePath;
 
-                _logger.Debug("Deleting previous image {0}", currentPath);
+                _logger.Info("Deleting previous image {0}", currentPath);
 
                 _libraryMonitor.ReportFileSystemChangeBeginning(currentPath);
 
@@ -236,7 +236,7 @@ namespace MediaBrowser.Providers.Manager
         /// <returns>Task.</returns>
         private async Task SaveImageToLocation(Stream source, string path, CancellationToken cancellationToken)
         {
-            _logger.Debug("Saving image to {0}", path);
+            _logger.Info("Saving image to {0}", path);
 
             var parentFolder = _fileSystem.GetDirectoryName(path);
 
@@ -249,31 +249,16 @@ namespace MediaBrowser.Providers.Manager
 
                 _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
 
-                // If the file is currently hidden we'll have to remove that or the save will fail
-                var file = _fileSystem.GetFileInfo(path);
+                _fileSystem.SetAttributes(path, false, false);
 
-                // This will fail if the file is hidden
-                if (file.Exists)
+                using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous))
                 {
-                    if (file.IsHidden)
-                    {
-                        _fileSystem.SetHidden(file.FullName, false);
-                    }
-                    if (file.IsReadOnly)
-                    {
-                        _fileSystem.SetReadOnly(path, false);
-                    }
-                }
-
-                using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
-                {
-                    await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken)
-                            .ConfigureAwait(false);
+                    await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
                 }
 
                 if (_config.Configuration.SaveMetadataHidden)
                 {
-                    _fileSystem.SetHidden(file.FullName, true);
+                    _fileSystem.SetHidden(path, true);
                 }
             }
             finally

+ 10 - 9
MediaBrowser.Providers/Omdb/OmdbItemProvider.cs

@@ -72,8 +72,7 @@ namespace MediaBrowser.Providers.Omdb
 
             var imdbId = searchInfo.GetProviderId(MetadataProviders.Imdb);
 
-            var baseUrl = await OmdbProvider.GetOmdbBaseUrl(cancellationToken).ConfigureAwait(false);
-            var url = baseUrl + "/?plot=full&r=json";
+            var urlQuery = "plot=full&r=json";
             if (type == "episode" && episodeSearchInfo != null)
             {
                 episodeSearchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out imdbId);
@@ -94,23 +93,23 @@ namespace MediaBrowser.Providers.Omdb
             {
                 if (year.HasValue)
                 {
-                    url += "&y=" + year.Value.ToString(CultureInfo.InvariantCulture);
+                    urlQuery += "&y=" + year.Value.ToString(CultureInfo.InvariantCulture);
                 }
 
                 // &s means search and returns a list of results as opposed to t
                 if (isSearch)
                 {
-                    url += "&s=" + WebUtility.UrlEncode(name);
+                    urlQuery += "&s=" + WebUtility.UrlEncode(name);
                 }
                 else
                 {
-                    url += "&t=" + WebUtility.UrlEncode(name);
+                    urlQuery += "&t=" + WebUtility.UrlEncode(name);
                 }
-                url += "&type=" + type;
+                urlQuery += "&type=" + type;
             }
             else
             {
-                url += "&i=" + imdbId;
+                urlQuery += "&i=" + imdbId;
                 isSearch = false;
             }
 
@@ -118,14 +117,16 @@ namespace MediaBrowser.Providers.Omdb
             {
                 if (searchInfo.IndexNumber.HasValue)
                 {
-                    url += string.Format(CultureInfo.InvariantCulture, "&Episode={0}", searchInfo.IndexNumber);
+                    urlQuery += string.Format(CultureInfo.InvariantCulture, "&Episode={0}", searchInfo.IndexNumber);
                 }
                 if (searchInfo.ParentIndexNumber.HasValue)
                 {
-                    url += string.Format(CultureInfo.InvariantCulture, "&Season={0}", searchInfo.ParentIndexNumber);
+                    urlQuery += string.Format(CultureInfo.InvariantCulture, "&Season={0}", searchInfo.ParentIndexNumber);
                 }
             }
 
+            var url = await OmdbProvider.GetOmdbUrl(urlQuery, cancellationToken).ConfigureAwait(false);
+
             using (var stream = await OmdbProvider.GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
             {
                 var resultList = new List<SearchResult>();

+ 11 - 6
MediaBrowser.Providers/Omdb/OmdbProvider.cs

@@ -265,9 +265,16 @@ namespace MediaBrowser.Providers.Omdb
             return false;
         }
 
-        public static async Task<string> GetOmdbBaseUrl(CancellationToken cancellationToken)
+        public static async Task<string> GetOmdbUrl(string query, CancellationToken cancellationToken)
         {
-            return "https://www.omdbapi.com";
+            var url = "https://www.omdbapi.com?apikey=fe53f97e";
+
+            if (!string.IsNullOrWhiteSpace(query))
+            {
+                url += "&" + query;
+            }
+
+            return url;
         }
 
         private async Task<string> EnsureItemInfo(string imdbId, CancellationToken cancellationToken)
@@ -292,8 +299,7 @@ namespace MediaBrowser.Providers.Omdb
                 }
             }
 
-            var baseUrl = await GetOmdbBaseUrl(cancellationToken).ConfigureAwait(false);
-            var url = string.Format(baseUrl + "/?i={0}&plot=full&tomatoes=true&r=json", imdbParam);
+            var url = await GetOmdbUrl(string.Format("i={0}&plot=full&tomatoes=true&r=json", imdbParam), cancellationToken).ConfigureAwait(false);
 
             using (var stream = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
             {
@@ -327,8 +333,7 @@ namespace MediaBrowser.Providers.Omdb
                 }
             }
 
-            var baseUrl = await GetOmdbBaseUrl(cancellationToken).ConfigureAwait(false);
-            var url = string.Format(baseUrl + "/?i={0}&season={1}&detail=full", imdbParam, seasonId);
+            var url = await GetOmdbUrl(string.Format("i={0}&season={1}&detail=full", imdbParam, seasonId), cancellationToken).ConfigureAwait(false);
 
             using (var stream = await GetOmdbResponse(_httpClient, url, cancellationToken).ConfigureAwait(false))
             {

+ 2 - 2
MediaBrowser.Server.Mac/Emby.Server.Mac.csproj

@@ -122,10 +122,10 @@
       <HintPath>..\ThirdParty\taglib\TagLib.Portable.dll</HintPath>
     </Reference>
     <Reference Include="SQLitePCLRaw.core">
-      <HintPath>..\packages\SQLitePCLRaw.core.1.1.2\lib\net45\SQLitePCLRaw.core.dll</HintPath>
+      <HintPath>..\packages\SQLitePCLRaw.core.1.1.5\lib\net45\SQLitePCLRaw.core.dll</HintPath>
     </Reference>
     <Reference Include="SQLitePCLRaw.provider.sqlite3">
-      <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.2\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
+      <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.5\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
     </Reference>
     <Reference Include="Emby.Server.Connect">
       <HintPath>..\ThirdParty\emby\Emby.Server.Connect.dll</HintPath>

+ 2 - 2
MediaBrowser.ServerApplication/ImageEncoderHelper.cs

@@ -26,11 +26,11 @@ namespace MediaBrowser.Server.Startup.Common
             {
                 try
                 {
-                    return new SkiaEncoder(logManager.GetLogger("ImageMagick"), appPaths, httpClient, fileSystem);
+                    return new SkiaEncoder(logManager.GetLogger("Skia"), appPaths, httpClient, fileSystem);
                 }
                 catch
                 {
-                    logger.Error("Error loading ImageMagick. Will revert to GDI.");
+                    logger.Error("Error loading Skia. Will revert to ImageMagick.");
                 }
 
                 try

+ 71 - 37
MediaBrowser.ServerApplication/MainStartup.cs

@@ -380,6 +380,9 @@ namespace MediaBrowser.ServerApplication
                 task = InstallVcredist2013IfNeeded(_appHost, _logger);
                 Task.WaitAll(task);
 
+                task = InstallVcredist2015IfNeeded(_appHost, _logger);
+                Task.WaitAll(task);
+
                 Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
 
                 HideSplashScreen();
@@ -736,32 +739,61 @@ namespace MediaBrowser.ServerApplication
             Process.Start(startInfo);
         }
 
-        private static bool CanRestartWindowsService()
+        private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger)
         {
-            var startInfo = new ProcessStartInfo
-            {
-                FileName = "cmd.exe",
-                CreateNoWindow = true,
-                WindowStyle = ProcessWindowStyle.Hidden,
-                Verb = "runas",
-                ErrorDialog = false,
-                Arguments = String.Format("/c sc query {0}", BackgroundService.GetExistingServiceName())
-            };
-            using (var process = Process.Start(startInfo))
+            // Reference 
+            // http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed
+
+            try
             {
-                process.WaitForExit();
-                if (process.ExitCode == 0)
-                {
-                    return true;
-                }
-                else
+                var subkey = Environment.Is64BitProcess
+                    ? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x64"
+                    : "SOFTWARE\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x86";
+
+                using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
+                    .OpenSubKey(subkey))
                 {
-                    return false;
+                    if (ndpKey != null && ndpKey.GetValue("Version") != null)
+                    {
+                        var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v');
+                        if (installedVersion.StartsWith("12", StringComparison.OrdinalIgnoreCase))
+                        {
+                            return;
+                        }
+                    }
                 }
             }
+            catch (Exception ex)
+            {
+                logger.ErrorException("Error getting .NET Framework version", ex);
+                return;
+            }
+
+            MessageBox.Show("The Visual C++ 2013 Runtime will now be installed.", "Install Visual C++ Runtime", MessageBoxButtons.OK, MessageBoxIcon.Information);
+
+            try
+            {
+                await InstallVcredist(GetVcredist2013Url()).ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                logger.ErrorException("Error installing Visual Studio C++ runtime", ex);
+            }
         }
 
-        private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger)
+        private static string GetVcredist2013Url()
+        {
+            if (Environment.Is64BitProcess)
+            {
+                return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_x64.exe";
+            }
+
+            // TODO: ARM url - https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_arm.exe
+
+            return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_x86.exe";
+        }
+
+        private static async Task InstallVcredist2015IfNeeded(ApplicationHost appHost, ILogger logger)
         {
             // Reference 
             // http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed
@@ -769,8 +801,8 @@ namespace MediaBrowser.ServerApplication
             try
             {
                 var subkey = Environment.Is64BitProcess
-                    ? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x64"
-                    : "SOFTWARE\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x86";
+                    ? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64"
+                    : "SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x86";
 
                 using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
                     .OpenSubKey(subkey))
@@ -778,7 +810,7 @@ namespace MediaBrowser.ServerApplication
                     if (ndpKey != null && ndpKey.GetValue("Version") != null)
                     {
                         var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v');
-                        if (installedVersion.StartsWith("12", StringComparison.OrdinalIgnoreCase))
+                        if (installedVersion.StartsWith("14", StringComparison.OrdinalIgnoreCase))
                         {
                             return;
                         }
@@ -791,9 +823,11 @@ namespace MediaBrowser.ServerApplication
                 return;
             }
 
+            MessageBox.Show("The Visual C++ 2015 Runtime will now be installed.", "Install Visual C++ Runtime", MessageBoxButtons.OK, MessageBoxIcon.Information);
+
             try
             {
-                await InstallVcredist2013().ConfigureAwait(false);
+                await InstallVcredist(GetVcredist2015Url()).ConfigureAwait(false);
             }
             catch (Exception ex)
             {
@@ -801,13 +835,25 @@ namespace MediaBrowser.ServerApplication
             }
         }
 
-        private async static Task InstallVcredist2013()
+        private static string GetVcredist2015Url()
+        {
+            if (Environment.Is64BitProcess)
+            {
+                return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2015/vc_redist.x64.exe";
+            }
+
+            // TODO: ARM url - https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2015/vcredist_arm.exe
+
+            return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2015/vc_redist.x86.exe";
+        }
+
+        private async static Task InstallVcredist(string url)
         {
             var httpClient = _appHost.HttpClient;
 
             var tmp = await httpClient.GetTempFile(new HttpRequestOptions
             {
-                Url = GetVcredist2013Url(),
+                Url = url,
                 Progress = new Progress<double>()
 
             }).ConfigureAwait(false);
@@ -833,18 +879,6 @@ namespace MediaBrowser.ServerApplication
             }
         }
 
-        private static string GetVcredist2013Url()
-        {
-            if (Environment.Is64BitProcess)
-            {
-                return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_x64.exe";
-            }
-
-            // TODO: ARM url - https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_arm.exe
-
-            return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_x86.exe";
-        }
-
         /// <summary>
         /// Sets the error mode.
         /// </summary>

+ 0 - 21
MediaBrowser.ServerApplication/Native/LoopUtil.cs

@@ -57,10 +57,6 @@ namespace MediaBrowser.ServerApplication.Native
         }
 
 
-        // Call this API to free the memory returned by the Enumeration API 
-        [DllImport("FirewallAPI.dll")]
-        internal static extern void NetworkIsolationFreeAppContainers(IntPtr pACs);
-
         // Call this API to load the current list of LoopUtil-enabled AppContainers
         [DllImport("FirewallAPI.dll")]
         internal static extern uint NetworkIsolationGetAppContainerConfig(out uint pdwCntACs, out IntPtr appContainerSids);
@@ -69,23 +65,13 @@ namespace MediaBrowser.ServerApplication.Native
         [DllImport("FirewallAPI.dll")]
         private static extern uint NetworkIsolationSetAppContainerConfig(uint pdwCntACs, SID_AND_ATTRIBUTES[] appContainerSids);
 
-
         // Use this API to convert a string SID into an actual SID 
         [DllImport("advapi32.dll", SetLastError = true)]
         internal static extern bool ConvertStringSidToSid(string strSid, out IntPtr pSid);
 
-        [DllImport("advapi32", /*CharSet = CharSet.Auto,*/ SetLastError = true)]
-        static extern bool ConvertSidToStringSid(
-            [MarshalAs(UnmanagedType.LPArray)] byte[] pSID,
-            out IntPtr ptrSid);
-
         [DllImport("advapi32", /*CharSet = CharSet.Auto,*/ SetLastError = true)]
         static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid);
 
-        // Use this API to convert a string reference (e.g. "@{blah.pri?ms-resource://whatever}") into a plain string 
-        [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
-        internal static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf);
-
         // Call this API to enumerate all of the AppContainers on the system 
         [DllImport("FirewallAPI.dll")]
         internal static extern uint NetworkIsolationEnumAppContainers(uint Flags, out uint pdwCntPublicACs, out IntPtr ppACs);
@@ -196,7 +182,6 @@ namespace MediaBrowser.ServerApplication.Native
             {
                 util.SaveLoopbackState();
             }
-            util.SaveLoopbackState();
         }
 
         private static List<SID_AND_ATTRIBUTES> PI_NetworkIsolationGetAppContainerConfig()
@@ -305,11 +290,5 @@ namespace MediaBrowser.ServerApplication.Native
             }
             return count;
         }
-
-        public void FreeResources()
-        {
-            NetworkIsolationFreeAppContainers(_pACs);
-        }
-
     }
 }

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

@@ -220,14 +220,9 @@ namespace MediaBrowser.XbmcMetadata.Savers
             {
                 if (file.IsHidden)
                 {
-                    FileSystem.SetHidden(path, false);
-
                     wasHidden = true;
                 }
-                if (file.IsReadOnly)
-                {
-                    FileSystem.SetReadOnly(path, false);
-                }
+                FileSystem.SetAttributes(path, false, false);
             }
 
             using (var filestream = FileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))

+ 1 - 1
SharedVersion.cs

@@ -1,3 +1,3 @@
 using System.Reflection;
 
-[assembly: AssemblyVersion("3.2.15.4")]
+[assembly: AssemblyVersion("3.2.15.5")]