diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs index 3ed4f650f7..7d14e521f3 100644 --- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs @@ -392,10 +392,27 @@ namespace Emby.Common.Implementations.IO if (_supportsAsyncFileStreams && isAsync) { - return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 262144, true); + return GetFileStream(path, mode, access, share, FileOpenOptions.Asynchronous); } - return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 262144); + return GetFileStream(path, mode, access, share, FileOpenOptions.None); + } + + public Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions) + { + if (_sharpCifsFileSystem.IsEnabledForPath(path)) + { + return _sharpCifsFileSystem.GetFileStream(path, mode, access, share); + } + + var defaultBufferSize = 4096; + return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), defaultBufferSize, GetFileOptions(fileOpenOptions)); + } + + private FileOptions GetFileOptions(FileOpenOptions mode) + { + var val = (int)mode; + return (FileOptions)val; } private FileMode GetFileMode(FileOpenMode mode) @@ -501,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; @@ -673,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); } diff --git a/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs b/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs index 0a407d64f5..64cac76230 100644 --- a/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs +++ b/Emby.Common.Implementations/IO/SharpCifsFileSystem.cs @@ -53,6 +53,11 @@ namespace Emby.Common.Implementations.IO if (separator == '/') { result = result.Replace('\\', '/'); + + if (result.StartsWith("smb:/", StringComparison.OrdinalIgnoreCase) && !result.StartsWith("smb://", StringComparison.OrdinalIgnoreCase)) + { + result = result.Replace("smb:/", "smb://"); + } } return result; @@ -161,23 +166,38 @@ namespace Emby.Common.Implementations.IO public void SetHidden(string path, bool isHidden) { var file = CreateSmbFile(path); - - var isCurrentlyHidden = file.IsHidden(); - - if (isCurrentlyHidden && !isHidden) - { - file.SetAttributes(file.GetAttributes() & ~SmbFile.AttrReadonly); - } - else if (!isCurrentlyHidden && isHidden) - { - file.SetAttributes(file.GetAttributes() | SmbFile.AttrReadonly); - } + 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.AttrHidden); + } + else if (!isCurrentlyHidden && isHidden) + { + file.SetAttributes(file.GetAttributes() | SmbFile.AttrHidden); + } + } + + private void SetReadOnly(SmbFile file, bool isReadOnly) + { var isCurrentlyReadOnly = !file.CanWrite(); if (isCurrentlyReadOnly && !isReadOnly) diff --git a/Emby.Common.Implementations/project.json b/Emby.Common.Implementations/project.json index 674101e8a7..ff60c740e8 100644 --- a/Emby.Common.Implementations/project.json +++ b/Emby.Common.Implementations/project.json @@ -45,7 +45,7 @@ "System.Net.Requests": "4.3.0", "System.Xml.ReaderWriter": "4.3.0", "System.Xml.XmlSerializer": "4.3.0", - "System.Net.Http": "4.3.0", + "System.Net.Http": "4.3.2", "System.Net.Primitives": "4.3.0", "System.Net.Sockets": "4.3.0", "System.Net.NetworkInformation": "4.3.0", diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 8f62e6d27a..b09dba70c3 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -587,10 +587,7 @@ namespace Emby.Dlna new DirectTvProfile(), new DishHopperJoeyProfile(), new DefaultProfile(), - new PopcornHourProfile(), - new VlcProfile(), - new BubbleUpnpProfile(), - new KodiProfile(), + new PopcornHourProfile() }; foreach (var item in list) diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index a8c05a5272..cae5a96362 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -79,13 +79,11 @@ - - @@ -105,7 +103,6 @@ - @@ -153,13 +150,11 @@ - - @@ -178,7 +173,6 @@ - diff --git a/Emby.Dlna/Profiles/BubbleUpnpProfile.cs b/Emby.Dlna/Profiles/BubbleUpnpProfile.cs deleted file mode 100644 index b551bff2ae..0000000000 --- a/Emby.Dlna/Profiles/BubbleUpnpProfile.cs +++ /dev/null @@ -1,146 +0,0 @@ -using MediaBrowser.Model.Dlna; -using System.Xml.Serialization; - -namespace Emby.Dlna.Profiles -{ - [XmlRoot("Profile")] - public class BubbleUpnpProfile : DefaultProfile - { - public BubbleUpnpProfile() - { - Name = "BubbleUPnp"; - - Identification = new DeviceIdentification - { - ModelName = "BubbleUPnp", - - Headers = new[] - { - new HttpHeaderInfo {Name = "User-Agent", Value = "BubbleUPnp", Match = HeaderMatchType.Substring} - } - }; - - TranscodingProfiles = new[] - { - new TranscodingProfile - { - Container = "mp3", - AudioCodec = "mp3", - Type = DlnaProfileType.Audio - }, - - new TranscodingProfile - { - Container = "ts", - Type = DlnaProfileType.Video, - AudioCodec = "aac", - VideoCodec = "h264" - }, - - new TranscodingProfile - { - Container = "jpeg", - Type = DlnaProfileType.Photo - } - }; - - DirectPlayProfiles = new[] - { - new DirectPlayProfile - { - Container = "", - Type = DlnaProfileType.Video - }, - - new DirectPlayProfile - { - Container = "", - Type = DlnaProfileType.Audio - }, - - new DirectPlayProfile - { - Container = "", - Type = DlnaProfileType.Photo, - } - }; - - ResponseProfiles = new ResponseProfile[] { }; - - ContainerProfiles = new ContainerProfile[] { }; - - CodecProfiles = new CodecProfile[] { }; - - SubtitleProfiles = new[] - { - new SubtitleProfile - { - Format = "srt", - Method = SubtitleDeliveryMethod.External, - }, - - new SubtitleProfile - { - Format = "sub", - Method = SubtitleDeliveryMethod.External, - }, - - new SubtitleProfile - { - Format = "srt", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "ass", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "ssa", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "smi", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "dvdsub", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "pgs", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "pgssub", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "sub", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - } - }; - } - } -} diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs index e99b22f2d0..1413e89d2a 100644 --- a/Emby.Dlna/Profiles/DefaultProfile.cs +++ b/Emby.Dlna/Profiles/DefaultProfile.cs @@ -30,8 +30,8 @@ namespace Emby.Dlna.Profiles MaxIconWidth = 48; MaxIconHeight = 48; - MaxStreamingBitrate = 30000000; - MaxStaticBitrate = 30000000; + MaxStreamingBitrate = 40000000; + MaxStaticBitrate = 40000000; MusicStreamingTranscodingBitrate = 192000; EnableAlbumArtInDidl = false; @@ -64,15 +64,13 @@ namespace Emby.Dlna.Profiles { new DirectPlayProfile { - Container = "m4v,ts,mpegts,mkv,avi,mpg,mpeg,mp4,mov", - VideoCodec = "h264", - AudioCodec = "aac,mp3,ac3", + Container = "m4v,mpegts,ts,3gp,mov,xvid,vob,mkv,wmv,asf,ogm,ogv,m2v,avi,mpg,mpeg,mp4,webm,wtv,m2ts,dvr-ms", Type = DlnaProfileType.Video }, new DirectPlayProfile { - Container = "mp3,wma,aac,wav,flac", + Container = "aac,mp3,mpa,wav,wma,mp2,ogg,oga,webma,ape,opus,flac", Type = DlnaProfileType.Audio } }; @@ -82,13 +80,61 @@ namespace Emby.Dlna.Profiles new SubtitleProfile { Format = "srt", - Method = SubtitleDeliveryMethod.Embed + Method = SubtitleDeliveryMethod.External, + }, + + new SubtitleProfile + { + Format = "sub", + Method = SubtitleDeliveryMethod.External, }, new SubtitleProfile { Format = "srt", - Method = SubtitleDeliveryMethod.External, + Method = SubtitleDeliveryMethod.Embed + }, + + new SubtitleProfile + { + Format = "ass", + Method = SubtitleDeliveryMethod.Embed + }, + + new SubtitleProfile + { + Format = "ssa", + Method = SubtitleDeliveryMethod.Embed + }, + + new SubtitleProfile + { + Format = "smi", + Method = SubtitleDeliveryMethod.Embed + }, + + new SubtitleProfile + { + Format = "dvdsub", + Method = SubtitleDeliveryMethod.Embed + }, + + new SubtitleProfile + { + Format = "pgs", + Method = SubtitleDeliveryMethod.Embed + }, + + new SubtitleProfile + { + Format = "pgssub", + Method = SubtitleDeliveryMethod.Embed + }, + + new SubtitleProfile + { + Format = "sub", + Method = SubtitleDeliveryMethod.Embed } }; diff --git a/Emby.Dlna/Profiles/KodiProfile.cs b/Emby.Dlna/Profiles/KodiProfile.cs deleted file mode 100644 index dbcac6652c..0000000000 --- a/Emby.Dlna/Profiles/KodiProfile.cs +++ /dev/null @@ -1,151 +0,0 @@ -using MediaBrowser.Model.Dlna; -using System.Xml.Serialization; - -namespace Emby.Dlna.Profiles -{ - [XmlRoot("Profile")] - public class KodiProfile : DefaultProfile - { - public KodiProfile() - { - Name = "Kodi"; - - MaxStreamingBitrate = 100000000; - MusicStreamingTranscodingBitrate = 1280000; - - TimelineOffsetSeconds = 5; - - Identification = new DeviceIdentification - { - ModelName = "Kodi", - - Headers = new[] - { - new HttpHeaderInfo {Name = "User-Agent", Value = "Kodi", Match = HeaderMatchType.Substring} - } - }; - - TranscodingProfiles = new[] - { - new TranscodingProfile - { - Container = "mp3", - AudioCodec = "mp3", - Type = DlnaProfileType.Audio - }, - - new TranscodingProfile - { - Container = "ts", - Type = DlnaProfileType.Video, - AudioCodec = "aac", - VideoCodec = "h264" - }, - - new TranscodingProfile - { - Container = "jpeg", - Type = DlnaProfileType.Photo - } - }; - - DirectPlayProfiles = new[] - { - new DirectPlayProfile - { - Container = "", - Type = DlnaProfileType.Video - }, - - new DirectPlayProfile - { - Container = "", - Type = DlnaProfileType.Audio - }, - - new DirectPlayProfile - { - Container = "", - Type = DlnaProfileType.Photo, - } - }; - - ResponseProfiles = new ResponseProfile[] { }; - - ContainerProfiles = new ContainerProfile[] { }; - - CodecProfiles = new CodecProfile[] { }; - - SubtitleProfiles = new[] - { - new SubtitleProfile - { - Format = "srt", - Method = SubtitleDeliveryMethod.External, - }, - - new SubtitleProfile - { - Format = "sub", - Method = SubtitleDeliveryMethod.External, - }, - - new SubtitleProfile - { - Format = "srt", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "ass", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "ssa", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "smi", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "dvdsub", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "pgs", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "pgssub", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "sub", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - } - }; - } - } -} diff --git a/Emby.Dlna/Profiles/VlcProfile.cs b/Emby.Dlna/Profiles/VlcProfile.cs deleted file mode 100644 index 2cd0e5cae7..0000000000 --- a/Emby.Dlna/Profiles/VlcProfile.cs +++ /dev/null @@ -1,149 +0,0 @@ -using MediaBrowser.Model.Dlna; -using System.Xml.Serialization; - -namespace Emby.Dlna.Profiles -{ - [XmlRoot("Profile")] - public class VlcProfile : DefaultProfile - { - public VlcProfile() - { - Name = "Vlc"; - - - TimelineOffsetSeconds = 5; - - Identification = new DeviceIdentification - { - ModelName = "Vlc", - - Headers = new[] - { - new HttpHeaderInfo {Name = "User-Agent", Value = "vlc", Match = HeaderMatchType.Substring} - } - }; - - TranscodingProfiles = new[] - { - new TranscodingProfile - { - Container = "mp3", - AudioCodec = "mp3", - Type = DlnaProfileType.Audio - }, - - new TranscodingProfile - { - Container = "ts", - Type = DlnaProfileType.Video, - AudioCodec = "aac", - VideoCodec = "h264" - }, - - new TranscodingProfile - { - Container = "jpeg", - Type = DlnaProfileType.Photo - } - }; - - DirectPlayProfiles = new[] - { - new DirectPlayProfile - { - Container = "", - Type = DlnaProfileType.Video - }, - - new DirectPlayProfile - { - Container = "", - Type = DlnaProfileType.Audio - }, - - new DirectPlayProfile - { - Container = "", - Type = DlnaProfileType.Photo, - } - }; - - ResponseProfiles = new ResponseProfile[] { }; - - ContainerProfiles = new ContainerProfile[] { }; - - CodecProfiles = new CodecProfile[] { }; - - SubtitleProfiles = new[] - { - new SubtitleProfile - { - Format = "srt", - Method = SubtitleDeliveryMethod.External, - }, - - new SubtitleProfile - { - Format = "sub", - Method = SubtitleDeliveryMethod.External, - }, - - new SubtitleProfile - { - Format = "srt", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "ass", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "ssa", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "smi", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "dvdsub", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "pgs", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "pgssub", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - }, - - new SubtitleProfile - { - Format = "sub", - Method = SubtitleDeliveryMethod.Embed, - DidlMode = "", - } - }; - } - } -} diff --git a/Emby.Dlna/Profiles/Xml/BubbleUPnp.xml b/Emby.Dlna/Profiles/Xml/BubbleUPnp.xml deleted file mode 100644 index 84bfa336a0..0000000000 --- a/Emby.Dlna/Profiles/Xml/BubbleUPnp.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - BubbleUPnp - - BubbleUPnp - - - - - Emby - http://emby.media/ - Emby Server - Emby - Emby - http://emby.media/ - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 30000000 - 30000000 - 192000 - - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 - 0 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Emby.Dlna/Profiles/Xml/Default.xml b/Emby.Dlna/Profiles/Xml/Default.xml index 6d39ac1da1..9c4e68d965 100644 --- a/Emby.Dlna/Profiles/Xml/Default.xml +++ b/Emby.Dlna/Profiles/Xml/Default.xml @@ -16,8 +16,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 @@ -29,8 +29,8 @@ false - - + + @@ -45,7 +45,15 @@ - + + + + + + + + + \ No newline at end of file diff --git a/Emby.Dlna/Profiles/Xml/Denon AVR.xml b/Emby.Dlna/Profiles/Xml/Denon AVR.xml index 9c6af71ed9..772e9a41c0 100644 --- a/Emby.Dlna/Profiles/Xml/Denon AVR.xml +++ b/Emby.Dlna/Profiles/Xml/Denon AVR.xml @@ -21,8 +21,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 @@ -45,7 +45,15 @@ - + + + + + + + + + \ No newline at end of file diff --git a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml index bb0fd0bb23..381a9f6411 100644 --- a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml +++ b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml @@ -22,8 +22,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml b/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml index fc84965105..4eb67ae81b 100644 --- a/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml +++ b/Emby.Dlna/Profiles/Xml/Dish Hopper-Joey.xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Kodi.xml b/Emby.Dlna/Profiles/Xml/Kodi.xml deleted file mode 100644 index 286417d968..0000000000 --- a/Emby.Dlna/Profiles/Xml/Kodi.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - Kodi - - Kodi - - - - - Emby - http://emby.media/ - Emby Server - Emby - Emby - http://emby.media/ - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 100000000 - 30000000 - 1280000 - - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 - 5 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml index e48af3842c..a61fefcc8b 100644 --- a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml +++ b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml @@ -22,8 +22,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml index 7f15181544..bd20112a8a 100644 --- a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml +++ b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml @@ -20,8 +20,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml index 3d5b398969..325c2e8368 100644 --- a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml +++ b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml @@ -22,8 +22,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 @@ -51,7 +51,15 @@ - + + + + + + + + + \ No newline at end of file diff --git a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml index 689f2813c9..1c58ab0c96 100644 --- a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml +++ b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml index 4da6fa3fa5..69bf74a987 100644 --- a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml +++ b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml @@ -16,8 +16,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml index fbdc7bc4fc..6fbed05900 100644 --- a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml +++ b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml @@ -22,8 +22,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml index 2ca6ff605a..ae2e686b85 100644 --- a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml +++ b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml @@ -22,8 +22,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml index d3735a9ed4..576f5ca500 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml +++ b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml @@ -26,8 +26,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml index c5c62cb737..5a04afb3e4 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml +++ b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml @@ -26,8 +26,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml index fd0ff91a02..97f0e37907 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml +++ b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml @@ -24,8 +24,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml index 66c942bfe3..052bc80d86 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml +++ b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml @@ -24,8 +24,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml index 7113a45830..084fa9df01 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml +++ b/Emby.Dlna/Profiles/Xml/Sony Blu-ray Player.xml @@ -24,8 +24,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml index e5fe83f0f3..046282ba39 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml index 8bbaefed9a..e30afa2af0 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml index 4e51116a02..cff07f04c4 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml index 002b31a27e..2d5794087d 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml index 2067a1d45b..d61b986c2b 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml index bea72c4104..966ecc2c8f 100644 --- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml +++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml index 128e0cf1fc..6a818193b8 100644 --- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml +++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Vlc.xml b/Emby.Dlna/Profiles/Xml/Vlc.xml deleted file mode 100644 index cd35f3f91a..0000000000 --- a/Emby.Dlna/Profiles/Xml/Vlc.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - Vlc - - Vlc - - - - - Emby - http://emby.media/ - Emby Server - Emby - Emby - http://emby.media/ - false - false - false - Audio,Photo,Video - JPEG_SM - 480 - 480 - 48 - 48 - 30000000 - 30000000 - 192000 - - DMS-1.50 - http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000 - 5 - false - false - false - false - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Emby.Dlna/Profiles/Xml/WDTV Live.xml b/Emby.Dlna/Profiles/Xml/WDTV Live.xml index acb9727034..71b3463187 100644 --- a/Emby.Dlna/Profiles/Xml/WDTV Live.xml +++ b/Emby.Dlna/Profiles/Xml/WDTV Live.xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Xbox 360.xml b/Emby.Dlna/Profiles/Xml/Xbox 360.xml index e46c52f4c8..3b7d2963f2 100644 --- a/Emby.Dlna/Profiles/Xml/Xbox 360.xml +++ b/Emby.Dlna/Profiles/Xml/Xbox 360.xml @@ -24,8 +24,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/Xbox One.xml b/Emby.Dlna/Profiles/Xml/Xbox One.xml index cffc386b1c..423327a389 100644 --- a/Emby.Dlna/Profiles/Xml/Xbox One.xml +++ b/Emby.Dlna/Profiles/Xml/Xbox One.xml @@ -23,8 +23,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 diff --git a/Emby.Dlna/Profiles/Xml/foobar2000.xml b/Emby.Dlna/Profiles/Xml/foobar2000.xml index 192ba5a300..febc12a8fa 100644 --- a/Emby.Dlna/Profiles/Xml/foobar2000.xml +++ b/Emby.Dlna/Profiles/Xml/foobar2000.xml @@ -22,8 +22,8 @@ 480 48 48 - 30000000 - 30000000 + 40000000 + 40000000 192000 DMS-1.50 @@ -51,7 +51,15 @@ - + + + + + + + + + \ No newline at end of file diff --git a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs index 500f57aade..f603c49509 100644 --- a/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs +++ b/Emby.Drawing.ImageMagick/ImageMagickEncoder.cs @@ -105,17 +105,6 @@ namespace Emby.Drawing.ImageMagick } } - public void CropWhiteSpace(string inputPath, string outputPath) - { - CheckDisposed(); - - using (var wand = new MagickWand(inputPath)) - { - wand.CurrentImage.TrimImage(10); - wand.SaveImage(outputPath); - } - } - public ImageSize GetImageSize(string path) { CheckDisposed(); @@ -150,6 +139,11 @@ namespace Emby.Drawing.ImageMagick { using (var originalImage = new MagickWand(inputPath)) { + if (options.CropWhiteSpace) + { + originalImage.CurrentImage.TrimImage(10); + } + ScaleImage(originalImage, width, height, options.Blur ?? 0); if (autoOrient) diff --git a/Emby.Drawing.Net/GDIImageEncoder.cs b/Emby.Drawing.Net/GDIImageEncoder.cs index 638415afdc..e710baaa77 100644 --- a/Emby.Drawing.Net/GDIImageEncoder.cs +++ b/Emby.Drawing.Net/GDIImageEncoder.cs @@ -75,27 +75,24 @@ namespace Emby.Drawing.Net } } - public void CropWhiteSpace(string inputPath, string outputPath) + private Image GetImage(string path, bool cropWhitespace) { - using (var image = (Bitmap)Image.FromFile(inputPath)) + if (cropWhitespace) { - using (var croppedImage = image.CropWhitespace()) + using (var originalImage = (Bitmap)Image.FromFile(path)) { - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(outputPath)); - - using (var outputStream = _fileSystem.GetFileStream(outputPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, false)) - { - croppedImage.Save(System.Drawing.Imaging.ImageFormat.Png, outputStream, 100); - } + return originalImage.CropWhitespace(); } } + + return Image.FromFile(path); } public void EncodeImage(string inputPath, string cacheFilePath, bool autoOrient, int width, int height, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) { var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed > 0; - using (var originalImage = Image.FromFile(inputPath)) + using (var originalImage = GetImage(inputPath, options.CropWhiteSpace)) { var newWidth = Convert.ToInt32(width); var newHeight = Convert.ToInt32(height); diff --git a/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj new file mode 100644 index 0000000000..d7b33b9507 --- /dev/null +++ b/Emby.Drawing.Skia/Emby.Drawing.Skia.csproj @@ -0,0 +1,80 @@ + + + + + 11.0 + Debug + AnyCPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD} + Library + Properties + Emby.Drawing.Skia + Emby.Drawing.Skia + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Profile7 + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + {9142eefa-7570-41e1-bfcc-468bb571af2f} + MediaBrowser.Common + + + {17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2} + MediaBrowser.Controller + + + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} + MediaBrowser.Model + + + + + Properties\SharedVersion.cs + + + + + + + + + + + ..\packages\SkiaSharp.1.57.1\lib\portable-net45+win8+wpa81+wp8\SkiaSharp.dll + True + + + + + + + + + \ No newline at end of file diff --git a/Emby.Drawing.Skia/PercentPlayedDrawer.cs b/Emby.Drawing.Skia/PercentPlayedDrawer.cs new file mode 100644 index 0000000000..e291a462b9 --- /dev/null +++ b/Emby.Drawing.Skia/PercentPlayedDrawer.cs @@ -0,0 +1,31 @@ +using SkiaSharp; +using MediaBrowser.Model.Drawing; +using System; + +namespace Emby.Drawing.Skia +{ + public class PercentPlayedDrawer + { + private const int IndicatorHeight = 8; + + public void Process(SKCanvas canvas, ImageSize imageSize, double percent) + { + using (var paint = new SKPaint()) + { + var endX = imageSize.Width - 1; + var endY = imageSize.Height - 1; + + paint.Color = SKColor.Parse("#99000000"); + paint.Style = SKPaintStyle.Fill; + canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, (float)endX, (float)endY), paint); + + double foregroundWidth = endX; + foregroundWidth *= percent; + foregroundWidth /= 100; + + paint.Color = SKColor.Parse("#FF52B54B"); + canvas.DrawRect(SKRect.Create(0, (float)endY - IndicatorHeight, Convert.ToInt32(Math.Round(foregroundWidth)), (float)endY), paint); + } + } + } +} diff --git a/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs b/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs new file mode 100644 index 0000000000..9f3a74eb7f --- /dev/null +++ b/Emby.Drawing.Skia/PlayedIndicatorDrawer.cs @@ -0,0 +1,120 @@ +using SkiaSharp; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Drawing; +using System; +using System.IO; +using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; +using System.Reflection; + +namespace Emby.Drawing.Skia +{ + public class PlayedIndicatorDrawer + { + private const int FontSize = 42; + private const int OffsetFromTopRightCorner = 38; + + private readonly IApplicationPaths _appPaths; + private readonly IHttpClient _iHttpClient; + private readonly IFileSystem _fileSystem; + + public PlayedIndicatorDrawer(IApplicationPaths appPaths, IHttpClient iHttpClient, IFileSystem fileSystem) + { + _appPaths = appPaths; + _iHttpClient = iHttpClient; + _fileSystem = fileSystem; + } + + public async Task DrawPlayedIndicator(SKCanvas canvas, ImageSize imageSize) + { + var x = imageSize.Width - OffsetFromTopRightCorner; + + using (var paint = new SKPaint()) + { + paint.Color = SKColor.Parse("#CC52B54B"); + paint.Style = SKPaintStyle.Fill; + canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint); + } + + using (var paint = new SKPaint()) + { + paint.Color = new SKColor(255, 255, 255, 255); + paint.Style = SKPaintStyle.Fill; + paint.Typeface = SKTypeface.FromFile(await DownloadFont("webdings.ttf", "https://github.com/MediaBrowser/Emby.Resources/raw/master/fonts/webdings.ttf", + _appPaths, _iHttpClient, _fileSystem).ConfigureAwait(false)); + paint.TextSize = FontSize; + paint.IsAntialias = true; + + canvas.DrawText("a", (float)x-20, OffsetFromTopRightCorner + 12, paint); + } + } + + internal static string ExtractFont(string name, IApplicationPaths paths, IFileSystem fileSystem) + { + var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name); + + if (fileSystem.FileExists(filePath)) + { + return filePath; + } + + var namespacePath = typeof(PlayedIndicatorDrawer).Namespace + ".fonts." + name; + var tempPath = Path.Combine(paths.TempDirectory, Guid.NewGuid().ToString("N") + ".ttf"); + fileSystem.CreateDirectory(fileSystem.GetDirectoryName(tempPath)); + + using (var stream = typeof(PlayedIndicatorDrawer).GetTypeInfo().Assembly.GetManifestResourceStream(namespacePath)) + { + using (var fileStream = fileSystem.GetFileStream(tempPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + { + stream.CopyTo(fileStream); + } + } + + fileSystem.CreateDirectory(fileSystem.GetDirectoryName(filePath)); + + try + { + fileSystem.CopyFile(tempPath, filePath, false); + } + catch (IOException) + { + + } + + return tempPath; + } + + internal static async Task DownloadFont(string name, string url, IApplicationPaths paths, IHttpClient httpClient, IFileSystem fileSystem) + { + var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name); + + if (fileSystem.FileExists(filePath)) + { + return filePath; + } + + var tempPath = await httpClient.GetTempFile(new HttpRequestOptions + { + Url = url, + Progress = new Progress() + + }).ConfigureAwait(false); + + fileSystem.CreateDirectory(fileSystem.GetDirectoryName(filePath)); + + try + { + fileSystem.CopyFile(tempPath, filePath, false); + } + catch (IOException) + { + + } + + return tempPath; + } + } +} diff --git a/Emby.Drawing.Skia/Properties/AssemblyInfo.cs b/Emby.Drawing.Skia/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c0dc7c5b4b --- /dev/null +++ b/Emby.Drawing.Skia/Properties/AssemblyInfo.cs @@ -0,0 +1,25 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Emby.Drawing.Skia")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Emby.Drawing.Skia")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// \ No newline at end of file diff --git a/Emby.Drawing.Skia/SkiaEncoder.cs b/Emby.Drawing.Skia/SkiaEncoder.cs new file mode 100644 index 0000000000..74ceb75910 --- /dev/null +++ b/Emby.Drawing.Skia/SkiaEncoder.cs @@ -0,0 +1,387 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using SkiaSharp; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Emby.Drawing.Skia +{ + public class SkiaEncoder : IImageEncoder + { + private readonly ILogger _logger; + private readonly IApplicationPaths _appPaths; + private readonly Func _httpClientFactory; + private readonly IFileSystem _fileSystem; + + public SkiaEncoder(ILogger logger, IApplicationPaths appPaths, Func httpClientFactory, IFileSystem fileSystem) + { + _logger = logger; + _appPaths = appPaths; + _httpClientFactory = httpClientFactory; + _fileSystem = fileSystem; + + LogVersion(); + } + + public string[] SupportedInputFormats + { + get + { + // Some common file name extensions for RAW picture files include: .cr2, .crw, .dng, .nef, .orf, .rw2, .pef, .arw, .sr2, .srf, and .tif. + return new[] + { + "jpeg", + "jpg", + "png", + "dng", + "webp", + "gif", + "bmp", + "ico", + "astc", + "ktx", + "pkm", + "wbmp" + }; + } + } + + public ImageFormat[] SupportedOutputFormats + { + get + { + return new[] { ImageFormat.Webp, ImageFormat.Gif, ImageFormat.Jpg, ImageFormat.Png, ImageFormat.Bmp }; + } + } + + private void LogVersion() + { + _logger.Info("SkiaSharp version: " + GetVersion()); + } + + public static string GetVersion() + { + using (var bitmap = new SKBitmap()) + { + return typeof(SKBitmap).GetTypeInfo().Assembly.GetName().Version.ToString(); + } + } + + private static bool IsWhiteSpace(SKColor color) + { + return (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0; + } + + public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat) + { + switch (selectedFormat) + { + case ImageFormat.Bmp: + return SKEncodedImageFormat.Bmp; + case ImageFormat.Jpg: + return SKEncodedImageFormat.Jpeg; + case ImageFormat.Gif: + return SKEncodedImageFormat.Gif; + case ImageFormat.Webp: + return SKEncodedImageFormat.Webp; + default: + return SKEncodedImageFormat.Png; + } + } + + private static bool IsAllWhiteRow(SKBitmap bmp, int row) + { + for (var i = 0; i < bmp.Width; ++i) + { + if (!IsWhiteSpace(bmp.GetPixel(i, row))) + { + return false; + } + } + return true; + } + + private static bool IsAllWhiteColumn(SKBitmap bmp, int col) + { + for (var i = 0; i < bmp.Height; ++i) + { + if (!IsWhiteSpace(bmp.GetPixel(col, i))) + { + return false; + } + } + return true; + } + + private SKBitmap CropWhiteSpace(SKBitmap bitmap) + { + var topmost = 0; + for (int row = 0; row < bitmap.Height; ++row) + { + if (IsAllWhiteRow(bitmap, row)) + topmost = row; + else break; + } + + int bottommost = 0; + for (int row = bitmap.Height - 1; row >= 0; --row) + { + if (IsAllWhiteRow(bitmap, row)) + bottommost = row; + else break; + } + + int leftmost = 0, rightmost = 0; + for (int col = 0; col < bitmap.Width; ++col) + { + if (IsAllWhiteColumn(bitmap, col)) + leftmost = col; + else + break; + } + + for (int col = bitmap.Width - 1; col >= 0; --col) + { + if (IsAllWhiteColumn(bitmap, col)) + rightmost = col; + else + break; + } + + var newRect = SKRectI.Create(leftmost, topmost, rightmost - leftmost, bottommost - topmost); + + using (var image = SKImage.FromBitmap(bitmap)) + { + using (var subset = image.Subset(newRect)) + { + return SKBitmap.FromImage(subset); + //using (var data = subset.Encode(StripCollageBuilder.GetEncodedFormat(outputPath), 90)) + //{ + // using (var fileStream = _fileSystem.GetFileStream(outputPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + // { + // data.AsStream().CopyTo(fileStream); + // } + //} + } + } + } + + public ImageSize GetImageSize(string path) + { + using (var s = new SKFileStream(path)) + { + using (var codec = SKCodec.Create(s)) + { + var info = codec.Info; + + return new ImageSize + { + Width = info.Width, + Height = info.Height + }; + } + } + } + + private string[] TransparentImageTypes = new string[] { ".png", ".gif", ".webp" }; + private SKBitmap Decode(string path) + { + var requiresTransparencyHack = TransparentImageTypes.Contains(Path.GetExtension(path) ?? string.Empty); + + if (requiresTransparencyHack) + { + using (var stream = new SKFileStream(path)) + { + var codec = SKCodec.Create(stream); + + // create the bitmap + var bitmap = new SKBitmap(codec.Info.Width, codec.Info.Height); + // decode + codec.GetPixels(bitmap.Info, bitmap.GetPixels()); + + return bitmap; + } + } + + return SKBitmap.Decode(path); + } + + private SKBitmap GetBitmap(string path, bool cropWhitespace) + { + if (cropWhitespace) + { + using (var bitmap = Decode(path)) + { + return CropWhiteSpace(bitmap); + } + } + + return Decode(path); + } + + public void EncodeImage(string inputPath, string outputPath, bool autoOrient, int width, int height, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat) + { + if (string.IsNullOrWhiteSpace(inputPath)) + { + throw new ArgumentNullException("inputPath"); + } + if (string.IsNullOrWhiteSpace(inputPath)) + { + throw new ArgumentNullException("outputPath"); + } + + var skiaOutputFormat = GetImageFormat(selectedOutputFormat); + + var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor); + var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer); + var blur = options.Blur ?? 0; + var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0); + + using (var bitmap = GetBitmap(inputPath, options.CropWhiteSpace)) + { + using (var resizedBitmap = new SKBitmap(width, height))//, bitmap.ColorType, bitmap.AlphaType)) + { + // scale image + var resizeMethod = SKBitmapResizeMethod.Lanczos3; + + bitmap.Resize(resizedBitmap, resizeMethod); + + // If all we're doing is resizing then we can stop now + if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) + { + using (var outputStream = new SKFileWStream(outputPath)) + { + resizedBitmap.Encode(outputStream, skiaOutputFormat, quality); + return; + } + } + + // create bitmap to use for canvas drawing + using (var saveBitmap = new SKBitmap(width, height))//, bitmap.ColorType, bitmap.AlphaType)) + { + // create canvas used to draw into bitmap + using (var canvas = new SKCanvas(saveBitmap)) + { + // set background color if present + if (hasBackgroundColor) + { + canvas.Clear(SKColor.Parse(options.BackgroundColor)); + } + + // Add blur if option is present + if (blur > 0) + { + using (var paint = new SKPaint()) + { + // create image from resized bitmap to apply blur + using (var filter = SKImageFilter.CreateBlur(blur, blur)) + { + paint.ImageFilter = filter; + canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height), paint); + } + } + } + else + { + // draw resized bitmap onto canvas + canvas.DrawBitmap(resizedBitmap, SKRect.Create(width, height)); + } + + // If foreground layer present then draw + if (hasForegroundColor) + { + Double opacity; + if (!Double.TryParse(options.ForegroundLayer, out opacity)) opacity = .4; + + canvas.DrawColor(new SKColor(0, 0, 0, (Byte)((1 - opacity) * 0xFF)), SKBlendMode.SrcOver); + } + + if (hasIndicator) + { + DrawIndicator(canvas, width, height, options); + } + + using (var outputStream = new SKFileWStream(outputPath)) + { + saveBitmap.Encode(outputStream, skiaOutputFormat, quality); + } + } + } + } + } + } + + public void CreateImageCollage(ImageCollageOptions options) + { + double ratio = options.Width; + ratio /= options.Height; + + if (ratio >= 1.4) + { + new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + } + else if (ratio >= .9) + { + new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + } + else + { + // @todo create Poster collage capability + new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths, options.OutputPath, options.Width, options.Height); + } + } + + private void DrawIndicator(SKCanvas canvas, int imageWidth, int imageHeight, ImageProcessingOptions options) + { + try + { + var currentImageSize = new ImageSize(imageWidth, imageHeight); + + if (options.AddPlayedIndicator) + { + var task = new PlayedIndicatorDrawer(_appPaths, _httpClientFactory(), _fileSystem).DrawPlayedIndicator(canvas, currentImageSize); + Task.WaitAll(task); + } + else if (options.UnplayedCount.HasValue) + { + new UnplayedCountIndicator(_appPaths, _httpClientFactory(), _fileSystem).DrawUnplayedCountIndicator(canvas, currentImageSize, options.UnplayedCount.Value); + } + + if (options.PercentPlayed > 0) + { + new PercentPlayedDrawer().Process(canvas, currentImageSize, options.PercentPlayed); + } + } + catch (Exception ex) + { + _logger.ErrorException("Error drawing indicator overlay", ex); + } + } + + public string Name + { + get { return "Skia"; } + } + + public void Dispose() + { + } + + public bool SupportsImageCollageCreation + { + get { return true; } + } + + public bool SupportsImageEncoding + { + get { return true; } + } + } +} \ No newline at end of file diff --git a/Emby.Drawing.Skia/StripCollageBuilder.cs b/Emby.Drawing.Skia/StripCollageBuilder.cs new file mode 100644 index 0000000000..605677aaba --- /dev/null +++ b/Emby.Drawing.Skia/StripCollageBuilder.cs @@ -0,0 +1,190 @@ +using SkiaSharp; +using MediaBrowser.Common.Configuration; +using System; +using System.IO; +using MediaBrowser.Model.IO; + +namespace Emby.Drawing.Skia +{ + public class StripCollageBuilder + { + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + + public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem) + { + _appPaths = appPaths; + _fileSystem = fileSystem; + } + + public static SKEncodedImageFormat GetEncodedFormat(string outputPath) + { + var ext = Path.GetExtension(outputPath).ToLower(); + + if (ext == ".jpg" || ext == ".jpeg") + return SKEncodedImageFormat.Jpeg; + + if (ext == ".webp") + return SKEncodedImageFormat.Webp; + + if (ext == ".gif") + return SKEncodedImageFormat.Gif; + + if (ext == ".bmp") + return SKEncodedImageFormat.Bmp; + + // default to png + return SKEncodedImageFormat.Png; + } + + public void BuildPosterCollage(string[] paths, string outputPath, int width, int height) + { + // @todo + } + + public void BuildSquareCollage(string[] paths, string outputPath, int width, int height) + { + using (var bitmap = BuildSquareCollageBitmap(paths, width, height)) + { + using (var outputStream = new SKFileWStream(outputPath)) + { + bitmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); + } + } + } + + public void BuildThumbCollage(string[] paths, string outputPath, int width, int height) + { + using (var bitmap = BuildThumbCollageBitmap(paths, width, height)) + { + using (var outputStream = new SKFileWStream(outputPath)) + { + bitmap.Encode(outputStream, GetEncodedFormat(outputPath), 90); + } + } + } + + private SKBitmap BuildThumbCollageBitmap(string[] paths, int width, int height) + { + var bitmap = new SKBitmap(width, height); + + using (var canvas = new SKCanvas(bitmap)) + { + canvas.Clear(SKColors.Black); + + // 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); + var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); + int imageIndex = 0; + + for (int i = 0; i < 4; i++) + { + 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))) + { + // draw image onto canvas + canvas.DrawImage(subset, (horizontalImagePadding * (i + 1)) + (iSlice * i), verticalSpacing); + + using (var croppedBitmap = SKBitmap.FromImage(subset)) + { + // create reflection of image below the drawn image + using (var reflectionBitmap = new SKBitmap(croppedBitmap.Width, croppedBitmap.Height / 2, croppedBitmap.ColorType, croppedBitmap.AlphaType)) + { + // resize to half height + croppedBitmap.Resize(reflectionBitmap, SKBitmapResizeMethod.Lanczos3); + + using (var flippedBitmap = new SKBitmap(reflectionBitmap.Width, reflectionBitmap.Height, reflectionBitmap.ColorType, reflectionBitmap.AlphaType)) + { + 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)); + } + } + } + } + } + } + } + } + + imageIndex++; + + if (imageIndex >= paths.Length) + imageIndex = 0; + } + } + + return bitmap; + } + + private SKBitmap BuildSquareCollageBitmap(string[] paths, int width, int height) + { + var bitmap = new SKBitmap(width, height); + var imageIndex = 0; + var cellWidth = width / 2; + var cellHeight = height / 2; + + using (var canvas = new SKCanvas(bitmap)) + { + for (var x = 0; x < 2; x++) + { + for (var y = 0; y < 2; y++) + { + using (var currentBitmap = SKBitmap.Decode(paths[imageIndex])) + { + using (var resizedBitmap = new SKBitmap(cellWidth, cellHeight, currentBitmap.ColorType, currentBitmap.AlphaType)) + { + // scale image + currentBitmap.Resize(resizedBitmap, SKBitmapResizeMethod.Lanczos3); + + // draw this image into the strip at the next position + var xPos = x * cellWidth; + var yPos = y * cellHeight; + canvas.DrawBitmap(resizedBitmap, xPos, yPos); + } + } + imageIndex++; + + if (imageIndex >= paths.Length) + imageIndex = 0; + } + } + } + + return bitmap; + } + } +} \ No newline at end of file diff --git a/Emby.Drawing.Skia/UnplayedCountIndicator.cs b/Emby.Drawing.Skia/UnplayedCountIndicator.cs new file mode 100644 index 0000000000..f0283ad23e --- /dev/null +++ b/Emby.Drawing.Skia/UnplayedCountIndicator.cs @@ -0,0 +1,68 @@ +using SkiaSharp; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Drawing; +using System.Globalization; +using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.IO; +using MediaBrowser.Model.IO; + +namespace Emby.Drawing.Skia +{ + public class UnplayedCountIndicator + { + private const int OffsetFromTopRightCorner = 38; + + private readonly IApplicationPaths _appPaths; + private readonly IHttpClient _iHttpClient; + private readonly IFileSystem _fileSystem; + + public UnplayedCountIndicator(IApplicationPaths appPaths, IHttpClient iHttpClient, IFileSystem fileSystem) + { + _appPaths = appPaths; + _iHttpClient = iHttpClient; + _fileSystem = fileSystem; + } + + public void DrawUnplayedCountIndicator(SKCanvas canvas, ImageSize imageSize, int count) + { + var x = imageSize.Width - OffsetFromTopRightCorner; + var text = count.ToString(CultureInfo.InvariantCulture); + + using (var paint = new SKPaint()) + { + paint.Color = SKColor.Parse("#CC52B54B"); + paint.Style = SKPaintStyle.Fill; + canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint); + } + using (var paint = new SKPaint()) + { + paint.Color = new SKColor(255, 255, 255, 255); + paint.Style = SKPaintStyle.Fill; + paint.Typeface = SKTypeface.FromFile(PlayedIndicatorDrawer.ExtractFont("robotoregular.ttf", _appPaths, _fileSystem)); + paint.TextSize = 24; + paint.IsAntialias = true; + + var y = OffsetFromTopRightCorner + 9; + + if (text.Length == 1) + { + x -= 7; + } + if (text.Length == 2) + { + x -= 13; + } + else if (text.Length >= 3) + { + x -= 15; + y -= 2; + paint.TextSize = 18; + } + + canvas.DrawText(text, (float)x, y, paint); + } + } + } +} diff --git a/Emby.Drawing.Skia/packages.config b/Emby.Drawing.Skia/packages.config new file mode 100644 index 0000000000..0743c38094 --- /dev/null +++ b/Emby.Drawing.Skia/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index bee0e9b69b..82181238bb 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -56,7 +56,7 @@ namespace Emby.Drawing private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationPaths _appPaths; - private readonly IImageEncoder _imageEncoder; + private IImageEncoder _imageEncoder; private readonly Func _libraryManager; public ImageProcessor(ILogger logger, @@ -64,7 +64,7 @@ namespace Emby.Drawing IFileSystem fileSystem, IJsonSerializer jsonSerializer, IImageEncoder imageEncoder, - int maxConcurrentImageProcesses, Func libraryManager, ITimerFactory timerFactory) + Func libraryManager, ITimerFactory timerFactory) { _logger = logger; _fileSystem = fileSystem; @@ -103,6 +103,20 @@ namespace Emby.Drawing _cachedImagedSizes = new ConcurrentDictionary(sizeDictionary); } + public IImageEncoder ImageEncoder + { + get { return _imageEncoder; } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + _imageEncoder = value; + } + } + public string[] SupportedInputFormats { get @@ -136,14 +150,6 @@ namespace Emby.Drawing } } - private string CroppedWhitespaceImageCachePath - { - get - { - return Path.Combine(_appPaths.ImageCachePath, "cropped-images"); - } - } - public void AddParts(IEnumerable enhancers) { ImageEnhancers = enhancers.ToArray(); @@ -186,14 +192,6 @@ namespace Emby.Drawing return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } - if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding) - { - var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); - - originalImagePath = tuple.Item1; - dateModified = tuple.Item2; - } - if (options.Enhancers.Count > 0) { var tuple = await GetEnhancedImage(new ItemImageInfo @@ -214,7 +212,7 @@ namespace Emby.Drawing return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } - ImageSize? originalImageSize; + ImageSize? originalImageSize = null; try { originalImageSize = GetImageSize(originalImagePath, dateModified, true); @@ -241,8 +239,8 @@ namespace Emby.Drawing if (!_fileSystem.FileExists(cacheFilePath)) { - var newWidth = Convert.ToInt32(newSize.Width); - var newHeight = Convert.ToInt32(newSize.Height); + var newWidth = Convert.ToInt32(Math.Round(newSize.Width)); + var newHeight = Convert.ToInt32(Math.Round(newSize.Height)); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFilePath)); var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath)); @@ -349,22 +347,22 @@ 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; if (width.HasValue) { - var heightValue = aspect / width.Value; - return new ImageSize(width.Value, Convert.ToInt32(heightValue)); + var heightValue = width.Value / aspect; + return new ImageSize(width.Value, heightValue); } var height = options.Height ?? options.MaxHeight ?? 200; var widthValue = aspect * height; - return new ImageSize(Convert.ToInt32(widthValue), height); + return new ImageSize(widthValue, height); } - private double GetEstimatedAspectRatio(ImageType type) + private double GetEstimatedAspectRatio(ImageType type, IHasImages item) { switch (type) { @@ -384,7 +382,7 @@ namespace Emby.Drawing case ImageType.Logo: return 2.58; case ImageType.Primary: - return .667; + return item.GetDefaultPrimaryImageAspectRatio() ?? .667; default: return 1; } @@ -400,46 +398,6 @@ namespace Emby.Drawing return requestedFormat; } - /// - /// Crops whitespace from an image, caches the result, and returns the cached path - /// - private async Task> GetWhitespaceCroppedImage(string originalImagePath, DateTime dateModified) - { - var name = originalImagePath; - name += "datemodified=" + dateModified.Ticks; - - var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath)); - - // Check again in case of contention - if (_fileSystem.FileExists(croppedImagePath)) - { - return GetResult(croppedImagePath); - } - - try - { - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(croppedImagePath)); - var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(croppedImagePath)); - _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(tmpPath)); - - _imageEncoder.CropWhiteSpace(originalImagePath, tmpPath); - CopyFile(tmpPath, croppedImagePath); - return GetResult(tmpPath); - } - catch (NotImplementedException) - { - // No need to spam the log with an error message - return new Tuple(originalImagePath, dateModified); - } - catch (Exception ex) - { - // We have to have a catch-all here because some of the .net image methods throw a plain old Exception - _logger.ErrorException("Error cropping image {0}", ex, originalImagePath); - - return new Tuple(originalImagePath, dateModified); - } - } - private Tuple GetResult(string path) { return new Tuple(path, _fileSystem.GetLastWriteTimeUtc(path)); @@ -555,26 +513,39 @@ namespace Emby.Drawing /// ImageSize. 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; diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index 4fa18ce553..c7d365fb2c 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/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() { } diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 78bdc11898..0fe30eb80c 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -187,7 +187,7 @@ namespace Emby.Server.Core /// The HTTP server. private IHttpServer HttpServer { get; set; } private IDtoService DtoService { get; set; } - private IImageProcessor ImageProcessor { get; set; } + public IImageProcessor ImageProcessor { get; set; } /// /// Gets or sets the media encoder. @@ -761,7 +761,10 @@ namespace Emby.Server.Core return null; } - X509Certificate2 localCert = new X509Certificate2(certificateLocation, info.Password); + // Don't use an empty string password + var password = string.IsNullOrWhiteSpace(info.Password) ? null : info.Password; + + X509Certificate2 localCert = new X509Certificate2(certificateLocation, password); //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; if (!localCert.HasPrivateKey) { @@ -780,14 +783,7 @@ namespace Emby.Server.Core private IImageProcessor GetImageProcessor() { - var maxConcurrentImageProcesses = Math.Max(Environment.ProcessorCount, 4); - - if (StartupOptions.ContainsOption("-imagethreads")) - { - int.TryParse(StartupOptions.GetOption("-imagethreads"), NumberStyles.Any, CultureInfo.InvariantCulture, out maxConcurrentImageProcesses); - } - - return new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, ImageEncoder, maxConcurrentImageProcesses, () => LibraryManager, TimerFactory); + return new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, ImageEncoder, () => LibraryManager, TimerFactory); } protected virtual FFMpegInstallInfo GetFfmpegInstallInfo() @@ -1132,7 +1128,8 @@ namespace Emby.Server.Core // Custom cert return new CertificateInfo { - Path = ServerConfigurationManager.Configuration.CertificatePath + Path = ServerConfigurationManager.Configuration.CertificatePath, + Password = ServerConfigurationManager.Configuration.CertificatePassword }; } diff --git a/Emby.Server.Core/IO/LibraryMonitor.cs b/Emby.Server.Core/IO/LibraryMonitor.cs index 8af826c881..0f0640a383 100644 --- a/Emby.Server.Core/IO/LibraryMonitor.cs +++ b/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); } diff --git a/Emby.Server.Core/project.json b/Emby.Server.Core/project.json index 70543d7df1..fd4f9d6cf7 100644 --- a/Emby.Server.Core/project.json +++ b/Emby.Server.Core/project.json @@ -68,7 +68,7 @@ "System.AppDomain": "2.0.11", "System.Globalization.Extensions": "4.3.0", "System.IO.FileSystem.Watcher": "4.3.0", - "System.Net.Security": "4.3.0", + "System.Net.Security": "4.3.1", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Runtime.Extensions": "4.3.0", "MediaBrowser.Model": { diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index bf88358465..e9b6f7a404 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.Activity { using (var statement = db.PrepareStatement("replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Id, @Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)")) { - statement.TryBind("@Id", entry.Id.ToGuidParamValue()); + statement.TryBind("@Id", entry.Id.ToGuidBlob()); statement.TryBind("@Name", entry.Name); statement.TryBind("@Overview", entry.Overview); @@ -168,7 +168,7 @@ namespace Emby.Server.Implementations.Activity var info = new ActivityLogEntry { - Id = reader[index].ReadGuid().ToString("N") + Id = reader[index].ReadGuidFromBlob().ToString("N") }; index++; diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 13874223cc..385b4bd518 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.AppBase Logger.Info("Saving system configuration"); var path = CommonApplicationPaths.SystemConfigurationFilePath; - FileSystem.CreateDirectory(Path.GetDirectoryName(path)); + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path)); lock (_configurationSyncLock) { @@ -293,7 +293,7 @@ namespace Emby.Server.Implementations.AppBase _configurations.AddOrUpdate(key, configuration, (k, v) => configuration); var path = GetConfigurationFile(key); - FileSystem.CreateDirectory(Path.GetDirectoryName(path)); + FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path)); lock (_configurationSyncLock) { diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index ad2f459459..d6a41dd67b 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.AppBase // If the file didn't exist before, or if something has changed, re-save if (buffer == null || !buffer.SequenceEqual(newBytes)) { - fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + fileSystem.CreateDirectory(fileSystem.GetDirectoryName(path)); // Save it after load in case we got new items fileSystem.WriteAllBytes(path, newBytes); diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index f3d84315e9..4118bd1b21 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -106,8 +106,8 @@ namespace Emby.Server.Implementations.Data using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)")) { - statement.TryBind("@id", displayPreferences.Id.ToGuidParamValue()); - statement.TryBind("@userId", userId.ToGuidParamValue()); + statement.TryBind("@id", displayPreferences.Id.ToGuidBlob()); + statement.TryBind("@userId", userId.ToGuidBlob()); statement.TryBind("@client", client); statement.TryBind("@data", serialized); @@ -170,8 +170,8 @@ namespace Emby.Server.Implementations.Data { using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client")) { - statement.TryBind("@id", guidId.ToGuidParamValue()); - statement.TryBind("@userId", userId.ToGuidParamValue()); + statement.TryBind("@id", guidId.ToGuidBlob()); + statement.TryBind("@userId", userId.ToGuidBlob()); statement.TryBind("@client", client); foreach (var row in statement.ExecuteQuery()) @@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId")) { - statement.TryBind("@userId", userId.ToGuidParamValue()); + statement.TryBind("@userId", userId.ToGuidBlob()); foreach (var row in statement.ExecuteQuery()) { diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index 783258a138..d2c851b3c6 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -26,17 +26,17 @@ namespace Emby.Server.Implementations.Data }); } - public static byte[] ToGuidParamValue(this string str) + public static byte[] ToGuidBlob(this string str) { - return ToGuidParamValue(new Guid(str)); + return ToGuidBlob(new Guid(str)); } - public static byte[] ToGuidParamValue(this Guid guid) + public static byte[] ToGuidBlob(this Guid guid) { return guid.ToByteArray(); } - public static Guid ReadGuid(this IResultSetValue result) + public static Guid ReadGuidFromBlob(this IResultSetValue result) { return new Guid(result.ToBlob()); } @@ -172,7 +172,7 @@ namespace Emby.Server.Implementations.Data public static Guid GetGuid(this IReadOnlyList result, int index) { - return result[index].ReadGuid(); + return result[index].ReadGuidFromBlob(); } private static void CheckName(string name) @@ -262,7 +262,7 @@ namespace Emby.Server.Implementations.Data IBindParameter bindParam; if (statement.BindParameters.TryGetValue(name, out bindParam)) { - bindParam.Bind(value.ToGuidParamValue()); + bindParam.Bind(value.ToGuidBlob()); } else { diff --git a/Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs b/Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs index 9fbe8669d1..a254962c94 100644 --- a/Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteFileOrganizationRepository.cs @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Data using (var statement = db.PrepareStatement(commandText)) { - statement.TryBind("@ResultId", result.Id.ToGuidParamValue()); + statement.TryBind("@ResultId", result.Id.ToGuidBlob()); statement.TryBind("@OriginalPath", result.OriginalPath); statement.TryBind("@TargetPath", result.TargetPath); @@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = db.PrepareStatement("delete from FileOrganizerResults where ResultId = @ResultId")) { - statement.TryBind("@ResultId", id.ToGuidParamValue()); + statement.TryBind("@ResultId", id.ToGuidBlob()); statement.MoveNext(); } }, TransactionMode); @@ -188,7 +188,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = connection.PrepareStatement("select ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths from FileOrganizerResults where ResultId=@ResultId")) { - statement.TryBind("@ResultId", id.ToGuidParamValue()); + statement.TryBind("@ResultId", id.ToGuidBlob()); foreach (var row in statement.ExecuteQuery()) { @@ -207,7 +207,7 @@ namespace Emby.Server.Implementations.Data var result = new FileOrganizationResult { - Id = reader[0].ReadGuid().ToString("N") + Id = reader[0].ReadGuidFromBlob().ToString("N") }; index++; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 61dce9bba3..28be49dc2f 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2128,7 +2128,7 @@ namespace Emby.Server.Implementations.Data connection.RunInTransaction(db => { // First delete chapters - db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", id.ToGuidParamValue()); + db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", id.ToGuidBlob()); using (var saveChapterStatement = PrepareStatement(db, "replace into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values (@ItemId, @ChapterIndex, @StartPositionTicks, @Name, @ImagePath, @ImageDateModified)")) { @@ -2139,7 +2139,7 @@ namespace Emby.Server.Implementations.Data saveChapterStatement.Reset(); } - saveChapterStatement.TryBind("@ItemId", id.ToGuidParamValue()); + saveChapterStatement.TryBind("@ItemId", id.ToGuidBlob()); saveChapterStatement.TryBind("@ChapterIndex", index); saveChapterStatement.TryBind("@StartPositionTicks", chapter.StartPositionTicks); saveChapterStatement.TryBind("@Name", chapter.Name); @@ -2919,7 +2919,7 @@ namespace Emby.Server.Implementations.Data foreach (var row in statement.ExecuteQuery()) { - list.Add(row[0].ReadGuid()); + list.Add(row[0].ReadGuidFromBlob()); } } @@ -3113,7 +3113,7 @@ namespace Emby.Server.Implementations.Data foreach (var row in statement.ExecuteQuery()) { - list.Add(row[0].ReadGuid()); + list.Add(row[0].ReadGuidFromBlob()); } } } @@ -3643,7 +3643,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(select Name from TypedBaseItems where guid=" + paramName + ") in (select Name from People where ItemId=Guid)"); if (statement != null) { - statement.TryBind(paramName, personId.ToGuidParamValue()); + statement.TryBind(paramName, personId.ToGuidBlob()); } index++; } @@ -3843,7 +3843,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type<=1)"); if (statement != null) { - statement.TryBind(paramName, artistId.ToGuidParamValue()); + statement.TryBind(paramName, artistId.ToGuidBlob()); } index++; } @@ -3862,7 +3862,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")"); if (statement != null) { - statement.TryBind(paramName, albumId.ToGuidParamValue()); + statement.TryBind(paramName, albumId.ToGuidBlob()); } index++; } @@ -3881,7 +3881,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from itemvalues where ItemId=Guid and Type<=1)"); if (statement != null) { - statement.TryBind(paramName, artistId.ToGuidParamValue()); + statement.TryBind(paramName, artistId.ToGuidBlob()); } index++; } @@ -3900,7 +3900,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=2)"); if (statement != null) { - statement.TryBind(paramName, genreId.ToGuidParamValue()); + statement.TryBind(paramName, genreId.ToGuidBlob()); } index++; } @@ -3953,7 +3953,7 @@ namespace Emby.Server.Implementations.Data clauses.Add("(select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from itemvalues where ItemId=Guid and Type=3)"); if (statement != null) { - statement.TryBind(paramName, studioId.ToGuidParamValue()); + statement.TryBind(paramName, studioId.ToGuidBlob()); } index++; } @@ -4521,22 +4521,22 @@ namespace Emby.Server.Implementations.Data connection.RunInTransaction(db => { // Delete people - ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", id.ToGuidParamValue()); + ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", id.ToGuidBlob()); // Delete chapters - ExecuteWithSingleParam(db, "delete from " + ChaptersTableName + " where ItemId=@Id", id.ToGuidParamValue()); + ExecuteWithSingleParam(db, "delete from " + ChaptersTableName + " where ItemId=@Id", id.ToGuidBlob()); // Delete media streams - ExecuteWithSingleParam(db, "delete from mediastreams where ItemId=@Id", id.ToGuidParamValue()); + ExecuteWithSingleParam(db, "delete from mediastreams where ItemId=@Id", id.ToGuidBlob()); // Delete ancestors - ExecuteWithSingleParam(db, "delete from AncestorIds where ItemId=@Id", id.ToGuidParamValue()); + ExecuteWithSingleParam(db, "delete from AncestorIds where ItemId=@Id", id.ToGuidBlob()); // Delete item values - ExecuteWithSingleParam(db, "delete from ItemValues where ItemId=@Id", id.ToGuidParamValue()); + ExecuteWithSingleParam(db, "delete from ItemValues where ItemId=@Id", id.ToGuidBlob()); // Delete the item - ExecuteWithSingleParam(db, "delete from TypedBaseItems where guid=@Id", id.ToGuidParamValue()); + ExecuteWithSingleParam(db, "delete from TypedBaseItems where guid=@Id", id.ToGuidBlob()); }, TransactionMode); } } @@ -4643,7 +4643,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("ItemId=@ItemId"); if (statement != null) { - statement.TryBind("@ItemId", query.ItemId.ToGuidParamValue()); + statement.TryBind("@ItemId", query.ItemId.ToGuidBlob()); } } if (query.AppearsInItemId != Guid.Empty) @@ -4651,7 +4651,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)"); if (statement != null) { - statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidParamValue()); + statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToGuidBlob()); } } var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList(); @@ -4730,14 +4730,14 @@ namespace Emby.Server.Implementations.Data // First delete deleteAncestorsStatement.Reset(); - deleteAncestorsStatement.TryBind("@ItemId", itemId.ToGuidParamValue()); + deleteAncestorsStatement.TryBind("@ItemId", itemId.ToGuidBlob()); deleteAncestorsStatement.MoveNext(); foreach (var ancestorId in ancestorIds) { updateAncestorsStatement.Reset(); - updateAncestorsStatement.TryBind("@ItemId", itemId.ToGuidParamValue()); - updateAncestorsStatement.TryBind("@AncestorId", ancestorId.ToGuidParamValue()); + updateAncestorsStatement.TryBind("@ItemId", itemId.ToGuidBlob()); + updateAncestorsStatement.TryBind("@AncestorId", ancestorId.ToGuidBlob()); updateAncestorsStatement.TryBind("@AncestorIdText", ancestorId.ToString("N")); updateAncestorsStatement.MoveNext(); } @@ -5198,7 +5198,7 @@ namespace Emby.Server.Implementations.Data CheckDisposed(); // First delete - db.Execute("delete from ItemValues where ItemId=@Id", itemId.ToGuidParamValue()); + db.Execute("delete from ItemValues where ItemId=@Id", itemId.ToGuidBlob()); using (var statement = PrepareStatement(db, "insert into ItemValues (ItemId, Type, Value, CleanValue) values (@ItemId, @Type, @Value, @CleanValue)")) { @@ -5214,7 +5214,7 @@ namespace Emby.Server.Implementations.Data statement.Reset(); - statement.TryBind("@ItemId", itemId.ToGuidParamValue()); + statement.TryBind("@ItemId", itemId.ToGuidBlob()); statement.TryBind("@Type", pair.Item1); statement.TryBind("@Value", itemValue); @@ -5252,7 +5252,7 @@ namespace Emby.Server.Implementations.Data { // First delete // "delete from People where ItemId=?" - connection.Execute("delete from People where ItemId=?", itemId.ToGuidParamValue()); + connection.Execute("delete from People where ItemId=?", itemId.ToGuidBlob()); var listIndex = 0; @@ -5266,7 +5266,7 @@ namespace Emby.Server.Implementations.Data statement.Reset(); } - statement.TryBind("@ItemId", itemId.ToGuidParamValue()); + statement.TryBind("@ItemId", itemId.ToGuidBlob()); statement.TryBind("@Name", person.Name); statement.TryBind("@Role", person.Role); statement.TryBind("@PersonType", person.Type); @@ -5339,7 +5339,7 @@ namespace Emby.Server.Implementations.Data using (var statement = PrepareStatementSafe(connection, cmdText)) { - statement.TryBind("@ItemId", query.ItemId.ToGuidParamValue()); + statement.TryBind("@ItemId", query.ItemId.ToGuidBlob()); if (query.Type.HasValue) { @@ -5383,7 +5383,7 @@ namespace Emby.Server.Implementations.Data using (var connection = CreateConnection()) { // First delete chapters - connection.Execute("delete from mediastreams where ItemId=@ItemId", id.ToGuidParamValue()); + connection.Execute("delete from mediastreams where ItemId=@ItemId", id.ToGuidBlob()); using (var statement = PrepareStatement(connection, string.Format("replace into mediastreams ({0}) values ({1})", string.Join(",", _mediaStreamSaveColumns), @@ -5393,7 +5393,7 @@ namespace Emby.Server.Implementations.Data { var paramList = new List(); - paramList.Add(id.ToGuidParamValue()); + paramList.Add(id.ToGuidBlob()); paramList.Add(stream.Index); paramList.Add(stream.Type.ToString()); paramList.Add(stream.Codec); diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index a31f0ed530..bf6388f5d8 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = db.PrepareStatement("replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)")) { - statement.TryBind("@userId", userId.ToGuidParamValue()); + statement.TryBind("@userId", userId.ToGuidBlob()); statement.TryBind("@key", key); if (userData.Rating.HasValue) @@ -311,7 +311,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where key =@Key and userId=@UserId")) { - statement.TryBind("@UserId", userId.ToGuidParamValue()); + statement.TryBind("@UserId", userId.ToGuidBlob()); statement.TryBind("@Key", key); foreach (var row in statement.ExecuteQuery()) @@ -364,7 +364,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where userId=@UserId")) { - statement.TryBind("@UserId", userId.ToGuidParamValue()); + statement.TryBind("@UserId", userId.ToGuidBlob()); foreach (var row in statement.ExecuteQuery()) { @@ -386,7 +386,7 @@ namespace Emby.Server.Implementations.Data var userData = new UserItemData(); userData.Key = reader[0].ToString(); - userData.UserId = reader[1].ReadGuid(); + userData.UserId = reader[1].ReadGuidFromBlob(); if (reader[2].SQLiteType != SQLiteType.Null) { diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index b2b917e5eb..29959bcabe 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -93,7 +93,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = db.PrepareStatement("replace into users (guid, data) values (@guid, @data)")) { - statement.TryBind("@guid", user.Id.ToGuidParamValue()); + statement.TryBind("@guid", user.Id.ToGuidBlob()); statement.TryBind("@data", serialized); statement.MoveNext(); } @@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.Data { foreach (var row in connection.Query("select guid,data from users")) { - var id = row[0].ReadGuid(); + var id = row[0].ReadGuidFromBlob(); using (var stream = _memoryStreamProvider.CreateNew(row[1].ToBlob())) { @@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = db.PrepareStatement("delete from users where guid=@id")) { - statement.TryBind("@id", user.Id.ToGuidParamValue()); + statement.TryBind("@id", user.Id.ToGuidBlob()); statement.MoveNext(); } }, TransactionMode); diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 588b42a093..b246ef1962 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Devices _libraryMonitor.ReportFileSystemChangeBeginning(path); - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); try { diff --git a/Emby.Server.Implementations/Devices/DeviceRepository.cs b/Emby.Server.Implementations/Devices/DeviceRepository.cs index f739765b34..de0dfda2ed 100644 --- a/Emby.Server.Implementations/Devices/DeviceRepository.cs +++ b/Emby.Server.Implementations/Devices/DeviceRepository.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Devices public Task SaveDevice(DeviceInfo device) { var path = Path.Combine(GetDevicePath(device.Id), "device.json"); - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); lock (_syncLock) { @@ -180,7 +180,7 @@ namespace Emby.Server.Implementations.Devices public void AddCameraUpload(string deviceId, LocalFileInfo file) { var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); - _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); lock (_syncLock) { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d4766e1ec7..b2f1f0ceb6 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -308,7 +308,7 @@ True - ..\packages\SQLitePCLRaw.core.1.1.2\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll + ..\packages\SQLitePCLRaw.core.1.1.5\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll True diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs index 2becebb3d3..3c8ad55fe4 100644 --- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs +++ b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs @@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.FFMpeg else { info = existingVersion; - versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath); + versionedDirectoryPath = _fileSystem.GetDirectoryName(info.EncoderPath); excludeFromDeletions.Add(versionedDirectoryPath); } } @@ -135,7 +135,7 @@ namespace Emby.Server.Implementations.FFMpeg { EncoderPath = encoder, ProbePath = probe, - Version = Path.GetFileName(Path.GetDirectoryName(probe)) + Version = Path.GetFileName(_fileSystem.GetDirectoryName(probe)) }; } } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index ee5245a69a..5e96eda94a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -228,7 +228,8 @@ namespace Emby.Server.Implementations.HttpServer _streamFactory, _enableDualModeSockets, GetRequest, - _fileSystem); + _fileSystem, + _environment); } private IHttpRequest GetRequest(HttpListenerContext httpContext) @@ -452,6 +453,7 @@ namespace Emby.Server.Implementations.HttpServer var date = DateTime.Now; var httpRes = httpReq.Response; bool enableLog = false; + bool logHeaders = false; string urlToLog = null; string remoteIp = null; @@ -490,13 +492,14 @@ namespace Emby.Server.Implementations.HttpServer var urlString = url.OriginalString; enableLog = EnableLogging(urlString, localPath); urlToLog = urlString; + logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1; if (enableLog) { urlToLog = GetUrlToLog(urlString); remoteIp = httpReq.RemoteIp; - LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent); + LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null); } if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) || @@ -611,7 +614,7 @@ namespace Emby.Server.Implementations.HttpServer var duration = DateTime.Now - date; - LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration); + LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration, logHeaders ? httpRes.Headers : null); } } } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 687bd62b06..0af88595f9 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -353,31 +353,28 @@ namespace Emby.Server.Implementations.HttpServer /// /// Pres the process optimized result. /// - /// The request context. - /// The responseHeaders. - /// The cache key. - /// The cache key string. - /// The last date modified. - /// Duration of the cache. - /// Type of the content. - /// System.Object. private object GetCachedResult(IRequest requestContext, IDictionary responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType) { responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString); - if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration)) + var noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; + + if (!noCache) { - AddAgeHeader(responseHeaders, lastDateModified); - AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration); + if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration)) + { + AddAgeHeader(responseHeaders, lastDateModified); + AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration, noCache); - var result = new HttpResult(new byte[] { }, contentType ?? "text/html", HttpStatusCode.NotModified); + var result = new HttpResult(new byte[] { }, contentType ?? "text/html", HttpStatusCode.NotModified); - AddResponseHeaders(result, responseHeaders); + AddResponseHeaders(result, responseHeaders); - return result; + return result; + } } - AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration); + AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration, noCache); return null; } @@ -673,11 +670,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// Adds the caching responseHeaders. /// - /// The responseHeaders. - /// The cache key. - /// The last date modified. - /// Duration of the cache. - private void AddCachingHeaders(IDictionary responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) + private void AddCachingHeaders(IDictionary responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, bool noCache) { // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching @@ -687,11 +680,11 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders["Last-Modified"] = lastDateModified.Value.ToString("r"); } - if (cacheDuration.HasValue) + if (!noCache && cacheDuration.HasValue) { responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds); } - else if (!string.IsNullOrEmpty(cacheKey)) + else if (!noCache && !string.IsNullOrEmpty(cacheKey)) { responseHeaders["Cache-Control"] = "public"; } @@ -701,18 +694,15 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders["pragma"] = "no-cache, no-store, must-revalidate"; } - AddExpiresHeader(responseHeaders, cacheKey, cacheDuration); + AddExpiresHeader(responseHeaders, cacheKey, cacheDuration, noCache); } /// /// Adds the expires header. /// - /// The responseHeaders. - /// The cache key. - /// Duration of the cache. - private void AddExpiresHeader(IDictionary responseHeaders, string cacheKey, TimeSpan? cacheDuration) + private void AddExpiresHeader(IDictionary responseHeaders, string cacheKey, TimeSpan? cacheDuration, bool noCache) { - if (cacheDuration.HasValue) + if (!noCache && cacheDuration.HasValue) { responseHeaders["Expires"] = DateTime.UtcNow.Add(cacheDuration.Value).ToString("r"); } diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs index 8fc92a09a7..f0e75eea48 100644 --- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs +++ b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs @@ -1,6 +1,8 @@ using MediaBrowser.Model.Logging; using System; using System.Globalization; +using System.Linq; +using MediaBrowser.Model.Services; using SocketHttpListener.Net; namespace Emby.Server.Implementations.HttpServer @@ -19,9 +21,18 @@ namespace Emby.Server.Implementations.HttpServer logger.Info("{0} {1}. UserAgent: {2}", request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty); } - public static void LogRequest(ILogger logger, string url, string method, string userAgent) + public static void LogRequest(ILogger logger, string url, string method, string userAgent, QueryParamCollection headers) { - logger.Info("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty); + if (headers == null) + { + logger.Info("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty); + } + else + { + var headerText = string.Join(", ", headers.Select(i => i.Name + "=" + i.Value).ToArray()); + + logger.Info("HTTP {0} {1}. {2}", method, url, headerText); + } } /// @@ -32,12 +43,13 @@ namespace Emby.Server.Implementations.HttpServer /// The URL. /// The end point. /// The duration. - public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration) + public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration, QueryParamCollection headers) { var durationMs = duration.TotalMilliseconds; var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms"; - logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url); + var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray()); + logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText); } } } diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index b11b2fe88f..682fa7a0b8 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -10,6 +10,7 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; +using MediaBrowser.Model.System; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; @@ -30,8 +31,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private readonly IFileSystem _fileSystem; private readonly Func _httpRequestFactory; private readonly bool _enableDualMode; + private readonly IEnvironmentInfo _environment; - public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func httpRequestFactory, IFileSystem fileSystem) + public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func httpRequestFactory, IFileSystem fileSystem, IEnvironmentInfo environment) { _logger = logger; _certificate = certificate; @@ -44,6 +46,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _enableDualMode = enableDualMode; _httpRequestFactory = httpRequestFactory; _fileSystem = fileSystem; + _environment = environment; } public Action ErrorHandler { get; set; } @@ -56,7 +59,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void Start(IEnumerable urlPrefixes) { if (_listener == null) - _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem); + _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem, _environment); _listener.EnableDualMode = _enableDualMode; diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index fd30b227f0..9e58ee57cd 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Services; using SocketHttpListener.Net; using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; @@ -66,6 +67,14 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _response.AddHeader(name, value); } + public QueryParamCollection Headers + { + get + { + return _response.Headers; + } + } + public string GetHeader(string name) { return _response.Headers[name]; diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 033cbd8b01..edff251563 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -34,8 +34,9 @@ namespace Emby.Server.Implementations.IO public event EventHandler 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); diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 2677f7b2ad..b2ec84a82a 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Images { return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false); } - if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre) + if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre || item is PhotoAlbum) { return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 685c794b76..3c94f97842 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1197,6 +1197,7 @@ namespace Emby.Server.Implementations.Library catch (OperationCanceledException) { _logger.Info("Post-scan task cancelled: {0}", task.GetType().Name); + throw; } catch (Exception ex) { diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index 643c5970e1..d4be2dabed 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators catch (OperationCanceledException) { // Don't clutter the log - break; + throw; } catch (Exception ex) { diff --git a/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs index b1820bb917..f7fbb93318 100644 --- a/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs @@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Validators catch (OperationCanceledException) { // Don't clutter the log - break; + throw; } catch (Exception ex) { diff --git a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs index d8956f78a1..d71e77a9a7 100644 --- a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs @@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library.Validators catch (OperationCanceledException) { // Don't clutter the log - break; + throw; } catch (Exception ex) { diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs index 983c881b75..98d53c1250 100644 --- a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.Library.Validators catch (OperationCanceledException) { // Don't clutter the log - break; + throw; } catch (Exception ex) { diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index 6faab7bb9d..97b8ff0ac4 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.Library.Validators catch (OperationCanceledException) { // Don't clutter the log - break; + throw; } catch (Exception ex) { diff --git a/Emby.Server.Implementations/Library/Validators/YearsPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/YearsPostScanTask.cs index ae43c77f0a..4afb4c04a7 100644 --- a/Emby.Server.Implementations/Library/Validators/YearsPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/YearsPostScanTask.cs @@ -26,6 +26,8 @@ namespace Emby.Server.Implementations.Library.Validators while (yearNumber < maxYear) { + cancellationToken.ThrowIfCancellationRequested(); + try { var year = _libraryManager.GetYear(yearNumber); @@ -35,7 +37,7 @@ namespace Emby.Server.Implementations.Library.Validators catch (OperationCanceledException) { // Don't clutter the log - break; + throw; } catch (Exception ex) { diff --git a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs index 76c7a7d77c..40752db800 100644 --- a/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs +++ b/Emby.Server.Implementations/Notifications/SqliteNotificationsRepository.cs @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.Notifications } clauses.Add("UserId=?"); - paramList.Add(query.UserId.ToGuidParamValue()); + paramList.Add(query.UserId.ToGuidBlob()); var whereClause = " where " + string.Join(" And ", clauses.ToArray()); @@ -133,7 +133,7 @@ namespace Emby.Server.Implementations.Notifications using (var statement = connection.PrepareStatement("select Level from Notifications where UserId=@UserId and IsRead=@IsRead")) { statement.TryBind("@IsRead", false); - statement.TryBind("@UserId", userId.ToGuidParamValue()); + statement.TryBind("@UserId", userId.ToGuidBlob()); var levels = new List(); @@ -159,8 +159,8 @@ namespace Emby.Server.Implementations.Notifications { var notification = new Notification { - Id = reader[0].ReadGuid().ToString("N"), - UserId = reader[1].ReadGuid().ToString("N"), + Id = reader[0].ReadGuidFromBlob().ToString("N"), + UserId = reader[1].ReadGuidFromBlob().ToString("N"), Date = reader[2].ReadDateTime(), Name = reader[3].ToString() }; @@ -251,8 +251,8 @@ namespace Emby.Server.Implementations.Notifications { using (var statement = conn.PrepareStatement("replace into Notifications (Id, UserId, Date, Name, Description, Url, Level, IsRead, Category, RelatedId) values (@Id, @UserId, @Date, @Name, @Description, @Url, @Level, @IsRead, @Category, @RelatedId)")) { - statement.TryBind("@Id", notification.Id.ToGuidParamValue()); - statement.TryBind("@UserId", notification.UserId.ToGuidParamValue()); + statement.TryBind("@Id", notification.Id.ToGuidBlob()); + statement.TryBind("@UserId", notification.UserId.ToGuidBlob()); statement.TryBind("@Date", notification.Date.ToDateTimeParamValue()); statement.TryBind("@Name", notification.Name); statement.TryBind("@Description", notification.Description); @@ -315,7 +315,7 @@ namespace Emby.Server.Implementations.Notifications using (var statement = conn.PrepareStatement("update Notifications set IsRead=@IsRead where UserId=@UserId")) { statement.TryBind("@IsRead", isRead); - statement.TryBind("@UserId", userId.ToGuidParamValue()); + statement.TryBind("@UserId", userId.ToGuidBlob()); statement.MoveNext(); } @@ -337,13 +337,13 @@ namespace Emby.Server.Implementations.Notifications using (var statement = conn.PrepareStatement("update Notifications set IsRead=@IsRead where UserId=@UserId and Id=@Id")) { statement.TryBind("@IsRead", isRead); - statement.TryBind("@UserId", userId.ToGuidParamValue()); + statement.TryBind("@UserId", userId.ToGuidBlob()); foreach (var id in notificationIdList) { statement.Reset(); - statement.TryBind("@Id", id.ToGuidParamValue()); + statement.TryBind("@Id", id.ToGuidBlob()); statement.MoveNext(); } diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index a2d61873b8..9ec0af6bb5 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -74,7 +74,7 @@ namespace Emby.Server.Implementations.Security { using (var statement = db.PrepareStatement("replace into AccessTokens (Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)")) { - statement.TryBind("@Id", info.Id.ToGuidParamValue()); + statement.TryBind("@Id", info.Id.ToGuidBlob()); statement.TryBind("@AccessToken", info.AccessToken); statement.TryBind("@DeviceId", info.DeviceId); @@ -259,7 +259,7 @@ namespace Emby.Server.Implementations.Security using (var statement = connection.PrepareStatement(commandText)) { - statement.BindParameters["@Id"].Bind(id.ToGuidParamValue()); + statement.BindParameters["@Id"].Bind(id.ToGuidBlob()); foreach (var row in statement.ExecuteQuery()) { @@ -275,7 +275,7 @@ namespace Emby.Server.Implementations.Security { var info = new AuthenticationInfo { - Id = reader[0].ReadGuid().ToString("N"), + Id = reader[0].ReadGuidFromBlob().ToString("N"), AccessToken = reader[1].ToString() }; diff --git a/Emby.Server.Implementations/Social/SharingRepository.cs b/Emby.Server.Implementations/Social/SharingRepository.cs index e8230947ec..46e9205bb2 100644 --- a/Emby.Server.Implementations/Social/SharingRepository.cs +++ b/Emby.Server.Implementations/Social/SharingRepository.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Social var commandText = "replace into Shares (Id, ItemId, UserId, ExpirationDate) values (?, ?, ?, ?)"; db.Execute(commandText, - info.Id.ToGuidParamValue(), + info.Id.ToGuidBlob(), info.ItemId, info.UserId, info.ExpirationDate.ToDateTimeParamValue()); @@ -84,7 +84,7 @@ namespace Emby.Server.Implementations.Social var commandText = "select Id, ItemId, UserId, ExpirationDate from Shares where id = ?"; var paramList = new List(); - paramList.Add(id.ToGuidParamValue()); + paramList.Add(id.ToGuidBlob()); foreach (var row in connection.Query(commandText, paramList.ToArray())) { @@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.Social { var info = new SocialShareInfo(); - info.Id = reader[0].ReadGuid().ToString("N"); + info.Id = reader[0].ReadGuidFromBlob().ToString("N"); info.ItemId = reader[1].ToString(); info.UserId = reader[2].ToString(); info.ExpirationDate = reader[3].ReadDateTime(); diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 57019cc4e2..e7aa402f25 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -664,9 +664,19 @@ namespace Emby.Server.Implementations.Updates // Remove it the quick way for now _applicationHost.RemovePlugin(plugin); - _logger.Info("Deleting plugin file {0}", plugin.AssemblyFilePath); + var path = plugin.AssemblyFilePath; + _logger.Info("Deleting plugin file {0}", path); - _fileSystem.DeleteFile(plugin.AssemblyFilePath); + // Make this case-insensitive to account for possible incorrect assembly naming + var file = _fileSystem.GetFilePaths(path) + .FirstOrDefault(i => string.Equals(i, path, StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrWhiteSpace(file)) + { + path = file; + } + + _fileSystem.DeleteFile(path); OnPluginUninstalled(plugin); diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index e4c75e1e92..5ce754d3ff 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/Emby.Server.sln b/Emby.Server.sln index c6e057c424..002a7ae0e9 100644 --- a/Emby.Server.sln +++ b/Emby.Server.sln @@ -54,8 +54,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Emby.Server.Core", "Emby.Se EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack", "ServiceStack\ServiceStack.csproj", "{680A1709-25EB-4D52-A87F-EE03FFD94BAA}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocketHttpListener.Portable", "SocketHttpListener.Portable\SocketHttpListener.Portable.csproj", "{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}" EndProject Global diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 0785b904ae..ff77497954 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -82,10 +82,6 @@ - - - - diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index daec00e10b..67921ab348 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -671,12 +671,15 @@ namespace MediaBrowser.Api.Playback request.AudioCodec = EncodingHelper.InferAudioCodec(url); } + var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) /*|| + string.Equals(Request.Headers.Get("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase)*/; + var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType) { Request = request, RequestedUrl = url, UserAgent = Request.UserAgent, - EnableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) + EnableDlnaHeaders = enableDlnaHeaders }; var auth = AuthorizationContext.GetAuthorizationInfo(Request); diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 0fe1e533d4..d64c009a07 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/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) diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 22c6202e4f..e32970be53 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/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; } diff --git a/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs b/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs index b25123df8a..e25e78802c 100644 --- a/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs +++ b/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs @@ -35,7 +35,8 @@ namespace MediaBrowser.Api.Reports Tracks, EpisodeSeries, EpisodeSeason, - AudioAlbumArtist, + EpisodeNumber, + AudioAlbumArtist, MusicArtist, AudioAlbum, Locked, diff --git a/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs b/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs index 39b2610d5c..6d5a180fb9 100644 --- a/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs +++ b/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs @@ -148,6 +148,11 @@ namespace MediaBrowser.Api.Reports /// The localized header. protected static string GetLocalizedHeader(HeaderMetadata internalHeader) { + if (internalHeader == HeaderMetadata.EpisodeNumber) + { + return "Episode"; + } + string headerName = ""; if (internalHeader != HeaderMetadata.None) { diff --git a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs index c9c63847c9..9c3dde6a46 100644 --- a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs +++ b/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(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()); diff --git a/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs b/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs deleted file mode 100644 index 52b095dee3..0000000000 --- a/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs +++ /dev/null @@ -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 -{ - /// A report stat builder. - /// - public class ReportStatBuilder : ReportBuilderBase - { - #region [Constructors] - - /// - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatBuilder class. - /// Manager for library. - public ReportStatBuilder(ILibraryManager libraryManager) - : base(libraryManager) - { - } - - #endregion - - #region [Public Methods] - - /// Gets report stat result. - /// The items. - /// List of types of the report include items. - /// The top item. - /// The report stat result. - 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] - /// Gets the headers. - /// Type of the header. - /// The request. - /// The headers. - /// - protected internal override List GetHeaders(H request) - { - throw new NotImplementedException(); - } - - #endregion - - #region [Private Methods] - - /// Gets the groups. - /// The result. - /// The header. - /// The top item. - /// The top. - private void GetGroups(ReportStatResult result, string header, int topItem, IEnumerable top) - { - if (top != null && top.Count() > 0) - { - var group = new ReportStatGroup { Header = ReportStatGroup.FormatedHeader(header, topItem) }; - group.Items.AddRange(top); - result.Groups.Add(group); - } - } - - /// Gets result community ratings. - /// The result. - /// The items. - /// The top item. - /// The result community ratings. - 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; - } - - /// Gets result genres. - /// The result. - /// The items. - /// The top item. - /// The result genres. - 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; - - } - - /// Gets result parental ratings. - /// The result. - /// The items. - /// The top item. - /// The result parental ratings. - 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; - } - - /// Gets result persons. - /// The result. - /// The items. - /// The top item. - /// The result persons. - private ReportStatResult GetResultPersons(ReportStatResult result, BaseItem[] items, int topItem = 5) - { - List t = new List - { - 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; - } - - /// Gets result production years. - /// The result. - /// The items. - /// The top item. - /// The result production years. - 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; - } - - /// Gets result studios. - /// The result. - /// The items. - /// The top item. - /// The result studios. - 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 - - } -} diff --git a/MediaBrowser.Api/Reports/Stat/ReportStatGroup.cs b/MediaBrowser.Api/Reports/Stat/ReportStatGroup.cs deleted file mode 100644 index f901b54173..0000000000 --- a/MediaBrowser.Api/Reports/Stat/ReportStatGroup.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Api.Reports -{ - /// A report stat group. - public class ReportStatGroup - { - /// - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatGroup class. - public ReportStatGroup() - { - Items = new List(); - TotalRecordCount = 0; - } - - /// Gets or sets the header. - /// The header. - public string Header { get; set; } - - /// Gets or sets the items. - /// The items. - public List Items { get; set; } - - /// Gets or sets the number of total records. - /// The total number of record count. - public int TotalRecordCount { get; set; } - - internal static string FormatedHeader(string header, int topItem) - { - return string.Format("Top {0} {1}", topItem, header); - } - } -} diff --git a/MediaBrowser.Api/Reports/Stat/ReportStatItem.cs b/MediaBrowser.Api/Reports/Stat/ReportStatItem.cs deleted file mode 100644 index c93ba15af9..0000000000 --- a/MediaBrowser.Api/Reports/Stat/ReportStatItem.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MediaBrowser.Api.Reports -{ - /// A report stat item. - public class ReportStatItem - { - /// Gets or sets the name. - /// The name. - public string Name { get; set; } - - /// Gets or sets the image. - /// The image. - public string Image { get; set; } - - /// Gets or sets the value. - /// The value. - public string Value { get; set; } - - /// Gets or sets the identifier. - /// The identifier. - public string Id { get; set; } - - } -} diff --git a/MediaBrowser.Api/Reports/Stat/ReportStatResult.cs b/MediaBrowser.Api/Reports/Stat/ReportStatResult.cs deleted file mode 100644 index fbf98fc17e..0000000000 --- a/MediaBrowser.Api/Reports/Stat/ReportStatResult.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Api.Reports -{ - /// Encapsulates the result of a report stat. - public class ReportStatResult - { - /// - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatResult class. - public ReportStatResult() - { - Groups = new List(); - TotalRecordCount = 0; - } - - /// Gets or sets the groups. - /// The groups. - public List Groups { get; set; } - - /// Gets or sets the number of total records. - /// The total number of record count. - public int TotalRecordCount { get; set; } - } -} diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index de1909e542..830093fcf2 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -16,12 +16,6 @@ namespace MediaBrowser.Controller.Drawing /// The supported output formats. ImageFormat[] SupportedOutputFormats { get; } /// - /// Crops the white space. - /// - /// The input path. - /// The output path. - void CropWhiteSpace(string inputPath, string outputPath); - /// /// Encodes the image. /// /// The input path. @@ -56,5 +50,7 @@ namespace MediaBrowser.Controller.Drawing /// /// true if [supports image encoding]; otherwise, false. bool SupportsImageEncoding { get; } + + ImageSize GetImageSize(string path); } } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 19f391b4a6..a107c12328 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -112,5 +112,7 @@ namespace MediaBrowser.Controller.Drawing /// /// true if [supports image collage creation]; otherwise, false. bool SupportsImageCollageCreation { get; } + + IImageEncoder ImageEncoder { get; set; } } } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index f4b3d94554..70ac083430 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -86,6 +86,7 @@ namespace MediaBrowser.Controller.Drawing PercentPlayed.Equals(0) && !UnplayedCount.HasValue && !Blur.HasValue && + !CropWhiteSpace && string.IsNullOrEmpty(BackgroundColor) && string.IsNullOrEmpty(ForegroundLayer); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ca0b97a9f6..11311905c2 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/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(); - 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(); + 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; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 28ada9daeb..409dec482c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/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 IgnoreIndex + public bool IgnoreInputIndex { - get { return MediaSource.IgnoreIndex; } + get + { + return MediaSource.IgnoreIndex; + } + } + + public bool GenPtsInput + { + get + { + return false; + } + } + + public bool DiscardCorruptFramesInput + { + get + { + return false; + } + } + + public bool EnableFastSeekInput + { + get + { + return false; + } + } + + public bool GenPtsOutput + { + get + { + return false; + } } public string OutputContainer { get; set; } diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index a3a55176cd..57e2ec450d 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/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)) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 15221c2acd..27f66835f5 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -50,10 +50,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } - if (logOutput) - { - _logger.Info("ffmpeg info: {0}", output); - } + _logger.Info("ffmpeg info: {0}", output); if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) { diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 8b20dca1b5..6270b87c65 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -176,6 +176,14 @@ namespace MediaBrowser.MediaEncoding.Probing info.Video3DFormat = Video3DFormat.FullSideBySide; } + foreach (var mediaStream in info.MediaStreams) + { + if (mediaStream.Type == MediaStreamType.Audio && !mediaStream.BitRate.HasValue) + { + mediaStream.BitRate = GetEstimatedAudioBitrate(mediaStream.Codec, mediaStream.Channels); + } + } + var videoStreamsBitrate = info.MediaStreams.Where(i => i.Type == MediaStreamType.Video).Select(i => i.BitRate ?? 0).Sum(); // If ffprobe reported the container bitrate as being the same as the video stream bitrate, then it's wrong if (videoStreamsBitrate == (info.Bitrate ?? 0)) @@ -187,6 +195,32 @@ namespace MediaBrowser.MediaEncoding.Probing return info; } + private int? GetEstimatedAudioBitrate(string codec, int? channels) + { + if (!channels.HasValue) + { + return null; + } + + var channelsValue = channels.Value; + + if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase) || + string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) + { + if (channelsValue <= 2) + { + return 192000; + } + + if (channelsValue >= 5) + { + return 320000; + } + } + + return null; + } + private void FetchFromItunesInfo(string xml, MediaInfo info) { // Make things simpler and strip out the dtd diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 838111a383..60bbf62404 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -55,6 +55,7 @@ namespace MediaBrowser.Model.Configuration /// /// The value pointing to the file system where the ssl certiifcate is located.. public string CertificatePath { get; set; } + public string CertificatePassword { get; set; } /// /// Gets or sets a value indicating whether this instance is port authorized. diff --git a/MediaBrowser.Model/Drawing/ImageSize.cs b/MediaBrowser.Model/Drawing/ImageSize.cs index 8cf09da185..c2b0291bdb 100644 --- a/MediaBrowser.Model/Drawing/ImageSize.cs +++ b/MediaBrowser.Model/Drawing/ImageSize.cs @@ -61,6 +61,12 @@ namespace MediaBrowser.Model.Drawing _height = height; } + public ImageSize(double width, double height) + { + _width = width; + _height = height; + } + private void ParseValue(string value) { if (!string.IsNullOrEmpty(value)) diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index 26de9332e3..ea6b048248 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -108,6 +108,8 @@ namespace MediaBrowser.Model.IO /// FileStream. Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false); + Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions); + /// /// Opens the read. /// @@ -311,7 +313,8 @@ namespace MediaBrowser.Model.IO IEnumerable 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; } @@ -402,4 +405,46 @@ namespace MediaBrowser.Model.IO ReadWrite = 3 } + // + // Summary: + // Represents advanced options for creating a System.IO.FileStream object. + [Flags] + public enum FileOpenOptions + { + // + // Summary: + // Indicates that the system should write through any intermediate cache and go + // directly to disk. + WriteThrough = int.MinValue, + // + // Summary: + // Indicates that no additional options should be used when creating a System.IO.FileStream + // object. + None = 0, + // + // Summary: + // Indicates that a file is encrypted and can be decrypted only by using the same + // user account used for encryption. + Encrypted = 16384, + // + // Summary: + // Indicates that a file is automatically deleted when it is no longer in use. + DeleteOnClose = 67108864, + // + // Summary: + // Indicates that the file is to be accessed sequentially from beginning to end. + // The system can use this as a hint to optimize file caching. If an application + // moves the file pointer for random access, optimum caching may not occur; however, + // correct operation is still guaranteed. + SequentialScan = 134217728, + // + // Summary: + // Indicates that the file is accessed randomly. The system can use this as a hint + // to optimize file caching. + RandomAccess = 268435456, + // + // Summary: + // Indicates that a file can be used for asynchronous reading and writing. + Asynchronous = 1073741824 + } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 115ba25ce0..f056c7410a 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -155,6 +155,8 @@ namespace MediaBrowser.Model.Services //Add Metadata to Response Dictionary Items { get; } + QueryParamCollection Headers { get; } + Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 1d8275c26f..4bd5044094 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/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 /// Task. 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 diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index f544c09dc1..d5494c21fd 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -200,6 +200,7 @@ namespace MediaBrowser.Providers.Manager MergeCriticRating(source, target, lockedFields, replaceData); MergeAwards(source, target, lockedFields, replaceData); MergeTrailers(source, target, lockedFields, replaceData); + MergeVideoInfo(source, target, lockedFields, replaceData); if (mergeMetadataSettings) { @@ -307,5 +308,19 @@ namespace MediaBrowser.Providers.Manager } } } + + private static void MergeVideoInfo(BaseItem source, BaseItem target, List lockedFields, bool replaceData) + { + var sourceCast = source as Video; + var targetCast = target as Video; + + if (sourceCast != null && targetCast != null) + { + if (replaceData || targetCast.Video3DFormat == null) + { + targetCast.Video3DFormat = sourceCast.Video3DFormat; + } + } + } } } diff --git a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs index 477543d5e2..f9d19b6be5 100644 --- a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs +++ b/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(); diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs index d1c3b22142..b89105376a 100644 --- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs @@ -265,9 +265,16 @@ namespace MediaBrowser.Providers.Omdb return false; } - public static async Task GetOmdbBaseUrl(CancellationToken cancellationToken) + public static async Task 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 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)) { diff --git a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj index 8133efafb8..73626328a2 100644 --- a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj +++ b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj @@ -122,10 +122,10 @@ ..\ThirdParty\taglib\TagLib.Portable.dll - ..\packages\SQLitePCLRaw.core.1.1.2\lib\net45\SQLitePCLRaw.core.dll + ..\packages\SQLitePCLRaw.core.1.1.5\lib\net45\SQLitePCLRaw.core.dll - ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.2\lib\net45\SQLitePCLRaw.provider.sqlite3.dll + ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.5\lib\net45\SQLitePCLRaw.provider.sqlite3.dll ..\ThirdParty\emby\Emby.Server.Connect.dll diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index bcdfa858ff..e4bde07c7d 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -94,11 +94,11 @@ True - ..\packages\SQLitePCLRaw.core.1.1.2\lib\net45\SQLitePCLRaw.core.dll + ..\packages\SQLitePCLRaw.core.1.1.5\lib\net45\SQLitePCLRaw.core.dll True - ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.2\lib\net45\SQLitePCLRaw.provider.sqlite3.dll + ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.5\lib\net45\SQLitePCLRaw.provider.sqlite3.dll True diff --git a/MediaBrowser.Server.Mono/packages.config b/MediaBrowser.Server.Mono/packages.config index 81da308f52..de26c7666e 100644 --- a/MediaBrowser.Server.Mono/packages.config +++ b/MediaBrowser.Server.Mono/packages.config @@ -5,6 +5,6 @@ - - + + \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/ImageEncoderHelper.cs b/MediaBrowser.ServerApplication/ImageEncoderHelper.cs index ddbde2f666..b8fa097d64 100644 --- a/MediaBrowser.ServerApplication/ImageEncoderHelper.cs +++ b/MediaBrowser.ServerApplication/ImageEncoderHelper.cs @@ -2,6 +2,7 @@ using Emby.Drawing; using Emby.Drawing.Net; using Emby.Drawing.ImageMagick; +using Emby.Drawing.Skia; using Emby.Server.Core; using Emby.Server.Implementations; using MediaBrowser.Common.Configuration; @@ -23,6 +24,15 @@ namespace MediaBrowser.Server.Startup.Common { if (!startupOptions.ContainsOption("-enablegdi")) { + try + { + return new SkiaEncoder(logManager.GetLogger("Skia"), appPaths, httpClient, fileSystem); + } + catch + { + logger.Error("Error loading Skia. Will revert to ImageMagick."); + } + try { return new ImageMagickEncoder(logManager.GetLogger("ImageMagick"), appPaths, httpClient, fileSystem); diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 8e38c9a987..272054609f 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -22,6 +22,7 @@ using Emby.Common.Implementations.IO; using Emby.Common.Implementations.Logging; using Emby.Common.Implementations.Networking; using Emby.Common.Implementations.Security; +using Emby.Drawing; using Emby.Server.Core; using Emby.Server.Core.Logging; using Emby.Server.Implementations; @@ -335,8 +336,6 @@ namespace MediaBrowser.ServerApplication var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), environmentInfo, appPaths.TempDirectory); - var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); - FileSystem = fileSystem; _appHost = new WindowsAppHost(appPaths, @@ -346,7 +345,7 @@ namespace MediaBrowser.ServerApplication new PowerManagement(), "emby.windows.zip", environmentInfo, - imageEncoder, + new NullImageEncoder(), new Server.Startup.Common.SystemEvents(logManager.GetLogger("SystemEvents")), new RecyclableMemoryStreamProvider(), new Networking.NetworkManager(logManager.GetLogger("NetworkManager")), @@ -367,6 +366,19 @@ namespace MediaBrowser.ServerApplication var task = _appHost.Init(initProgress); Task.WaitAll(task); + if (!runService) + { + task = InstallVcredist2013IfNeeded(_appHost, _logger); + Task.WaitAll(task); + + // needed by skia + task = InstallVcredist2015IfNeeded(_appHost, _logger); + Task.WaitAll(task); + } + + // set image encoder here + _appHost.ImageProcessor.ImageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); + task = task.ContinueWith(new Action(a => _appHost.RunStartupTasks()), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); if (runService) @@ -377,9 +389,6 @@ namespace MediaBrowser.ServerApplication { Task.WaitAll(task); - task = InstallVcredist2013IfNeeded(_appHost, _logger); - Task.WaitAll(task); - Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionSwitch; HideSplashScreen(); @@ -736,31 +745,6 @@ namespace MediaBrowser.ServerApplication Process.Start(startInfo); } - private static bool CanRestartWindowsService() - { - 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)) - { - process.WaitForExit(); - if (process.ExitCode == 0) - { - return true; - } - else - { - return false; - } - } - } - private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger) { // Reference @@ -791,9 +775,11 @@ namespace MediaBrowser.ServerApplication return; } + MessageBox.Show("The Visual C++ 2013 Runtime will now be installed.", "Install Visual C++ Runtime", MessageBoxButtons.OK, MessageBoxIcon.Information); + try { - await InstallVcredist2013().ConfigureAwait(false); + await InstallVcredist(GetVcredist2013Url()).ConfigureAwait(false); } catch (Exception ex) { @@ -801,13 +787,79 @@ namespace MediaBrowser.ServerApplication } } - private async static Task InstallVcredist2013() + 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 + + try + { + var subkey = Environment.Is64BitProcess + ? "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)) + { + if (ndpKey != null && ndpKey.GetValue("Version") != null) + { + var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v'); + if (installedVersion.StartsWith("14", StringComparison.OrdinalIgnoreCase)) + { + return; + } + } + } + } + catch (Exception ex) + { + logger.ErrorException("Error getting .NET Framework version", ex); + return; + } + + MessageBox.Show("The Visual C++ 2015 Runtime will now be installed.", "Install Visual C++ Runtime", MessageBoxButtons.OK, MessageBoxIcon.Information); + + try + { + await InstallVcredist(GetVcredist2015Url()).ConfigureAwait(false); + } + catch (Exception ex) + { + logger.ErrorException("Error installing Visual Studio C++ runtime", ex); + } + } + + 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() }).ConfigureAwait(false); @@ -833,18 +885,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"; - } - /// /// Sets the error mode. /// diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 749468fe2b..d632007d20 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -99,12 +99,16 @@ ..\packages\SimpleInjector.3.3.2\lib\net45\SimpleInjector.dll True + + ..\packages\SkiaSharp.1.57.1\lib\net45\SkiaSharp.dll + True + - ..\packages\SQLitePCLRaw.core.1.1.2\lib\net45\SQLitePCLRaw.core.dll + ..\packages\SQLitePCLRaw.core.1.1.5\lib\net45\SQLitePCLRaw.core.dll True - ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.2\lib\net45\SQLitePCLRaw.provider.sqlite3.dll + ..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.5\lib\net45\SQLitePCLRaw.provider.sqlite3.dll True @@ -185,6 +189,14 @@ + + x64\libSkiaSharp.dll + PreserveNewest + + + x86\libSkiaSharp.dll + PreserveNewest + MediaBrowser.InstallUtil.dll PreserveNewest @@ -1100,6 +1112,10 @@ {c97a239e-a96c-4d64-a844-ccf8cc30aecb} Emby.Drawing.Net + + {2312da6d-ff86-4597-9777-bceec32d96dd} + Emby.Drawing.Skia + {08fff49b-f175-4807-a2b5-73b0ebd9f716} Emby.Drawing diff --git a/MediaBrowser.ServerApplication/Native/LoopUtil.cs b/MediaBrowser.ServerApplication/Native/LoopUtil.cs index 9a96f5518e..7c74712312 100644 --- a/MediaBrowser.ServerApplication/Native/LoopUtil.cs +++ b/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 PI_NetworkIsolationGetAppContainerConfig() @@ -305,11 +290,5 @@ namespace MediaBrowser.ServerApplication.Native } return count; } - - public void FreeResources() - { - NetworkIsolationFreeAppContainers(_pACs); - } - } } diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index 68d0a7fdad..2d4ba61707 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/MediaBrowser.ServerApplication/packages.config @@ -5,6 +5,7 @@ - - + + + \ No newline at end of file diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index dfd4694c36..d8f7cb57f4 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -15,6 +15,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Xml; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.IO; using MediaBrowser.Model.Xml; @@ -227,6 +228,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } + protected virtual string MovieDbParserSearchString + { + get { return "themoviedb.org/movie/"; } + } + private void ParseProviderLinks(T item, string xml) { //Look for a match for the Regex pattern "tt" followed by 7 digits @@ -238,7 +244,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers // Support Tmdb // http://www.themoviedb.org/movie/36557 - var srch = "themoviedb.org/movie/"; + var srch = MovieDbParserSearchString; var index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase); if (index != -1) @@ -250,6 +256,23 @@ namespace MediaBrowser.XbmcMetadata.Parsers item.SetProviderId(MetadataProviders.Tmdb, tmdbId); } } + + if (item is Series) + { + srch = "thetvdb.com/?tab=series&id="; + + index = xml.IndexOf(srch, StringComparison.OrdinalIgnoreCase); + + if (index != -1) + { + var tvdbId = xml.Substring(index + srch.Length).TrimEnd('/'); + int value; + if (!string.IsNullOrWhiteSpace(tvdbId) && int.TryParse(tvdbId, NumberStyles.Any, CultureInfo.InvariantCulture, out value)) + { + item.SetProviderId(MetadataProviders.Tvdb, tvdbId); + } + } + } } protected virtual void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult) diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index 98016f4f70..b0db4e6f38 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -13,6 +13,19 @@ namespace MediaBrowser.XbmcMetadata.Parsers { public class SeriesNfoParser : BaseNfoParser { + protected override bool SupportsUrlAfterClosingXmlTag + { + get + { + return true; + } + } + + protected override string MovieDbParserSearchString + { + get { return "themoviedb.org/tv/"; } + } + /// /// Fetches the data from XML node. /// diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 623b109f76..ae24928028 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/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)) diff --git a/MediaBrowser.sln b/MediaBrowser.sln index b9933969f5..219beeab16 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -80,6 +80,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.Net", "Emby.Dr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocketHttpListener.Portable", "SocketHttpListener.Portable\SocketHttpListener.Portable.csproj", "{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.Skia", "Emby.Drawing.Skia\Emby.Drawing.Skia.csproj", "{2312DA6D-FF86-4597-9777-BCEEC32D96DD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1099,6 +1101,46 @@ Global {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|x64.Build.0 = Release|Any CPU {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|x86.ActiveCfg = Release|Any CPU {4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|x86.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Win32.ActiveCfg = Debug|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Win32.Build.0 = Debug|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|x64.Build.0 = Debug|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|x86.Build.0 = Debug|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Win32.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Win32.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|x64.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|x64.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|x86.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|x86.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Any CPU.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Win32.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Win32.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|x64.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|x64.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|x86.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|x86.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Any CPU.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Any CPU.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Mixed Platforms.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Win32.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Win32.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x64.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x64.Build.0 = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x86.ActiveCfg = Release|Any CPU + {2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index c86e9a71cd..64e4d14181 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.699 + 3.0.700 Emby.Common Emby Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index f68cf4e410..917288cb1e 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.699 + 3.0.700 Emby.Server.Core Emby Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Emby Server. Copyright © Emby 2013 - + diff --git a/SocketHttpListener.Portable/Net/EndPointListener.cs b/SocketHttpListener.Portable/Net/EndPointListener.cs index 4f1a17fc0d..2106bbec56 100644 --- a/SocketHttpListener.Portable/Net/EndPointListener.cs +++ b/SocketHttpListener.Portable/Net/EndPointListener.cs @@ -8,6 +8,7 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; @@ -33,8 +34,9 @@ namespace SocketHttpListener.Net private readonly ITextEncoding _textEncoding; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly IFileSystem _fileSystem; + private readonly IEnvironmentInfo _environment; - public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem) + public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) { this.listener = listener; _logger = logger; @@ -44,6 +46,7 @@ namespace SocketHttpListener.Net _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; _fileSystem = fileSystem; + _environment = environment; this.secure = secure; this.cert = cert; @@ -109,7 +112,7 @@ namespace SocketHttpListener.Net return; } - HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding, _fileSystem).ConfigureAwait(false); + HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding, _fileSystem, _environment).ConfigureAwait(false); //_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); lock (listener.unregistered) diff --git a/SocketHttpListener.Portable/Net/EndPointManager.cs b/SocketHttpListener.Portable/Net/EndPointManager.cs index 11f7749153..6a00ed360a 100644 --- a/SocketHttpListener.Portable/Net/EndPointManager.cs +++ b/SocketHttpListener.Portable/Net/EndPointManager.cs @@ -106,7 +106,7 @@ namespace SocketHttpListener.Net } else { - epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem); + epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem, listener.EnvironmentInfo); p[port] = epl; } diff --git a/SocketHttpListener.Portable/Net/HttpConnection.cs b/SocketHttpListener.Portable/Net/HttpConnection.cs index ac8ada4862..65e7470f7e 100644 --- a/SocketHttpListener.Portable/Net/HttpConnection.cs +++ b/SocketHttpListener.Portable/Net/HttpConnection.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; @@ -41,8 +42,9 @@ namespace SocketHttpListener.Net private readonly ITextEncoding _textEncoding; private readonly IStreamFactory _streamFactory; private readonly IFileSystem _fileSystem; + private readonly IEnvironmentInfo _environment; - private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem) + private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) { _logger = logger; this.sock = sock; @@ -53,6 +55,7 @@ namespace SocketHttpListener.Net _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; _fileSystem = fileSystem; + _environment = environment; _streamFactory = streamFactory; } @@ -84,9 +87,9 @@ namespace SocketHttpListener.Net Init(); } - public static async Task Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem) + public static async Task Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment) { - var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding, fileSystem); + var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding, fileSystem, environment); await connection.InitStream().ConfigureAwait(false); @@ -217,7 +220,7 @@ namespace SocketHttpListener.Net { var supportsDirectSocketAccess = !context.Response.SendChunked && !isExpect100Continue && !secure; - o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess, _logger); + o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess, _logger, _environment); } else { diff --git a/SocketHttpListener.Portable/Net/HttpListener.cs b/SocketHttpListener.Portable/Net/HttpListener.cs index c2e7acd8e7..b3e01425ca 100644 --- a/SocketHttpListener.Portable/Net/HttpListener.cs +++ b/SocketHttpListener.Portable/Net/HttpListener.cs @@ -8,6 +8,7 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; @@ -22,6 +23,7 @@ namespace SocketHttpListener.Net internal ITextEncoding TextEncoding { get; private set; } internal IMemoryStreamFactory MemoryStreamFactory { get; private set; } internal INetworkManager NetworkManager { get; private set; } + internal IEnvironmentInfo EnvironmentInfo { get; private set; } public bool EnableDualMode { get; set; } @@ -40,7 +42,7 @@ namespace SocketHttpListener.Net public Action OnContext { get; set; } - public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem) + public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) { _logger = logger; CryptoProvider = cryptoProvider; @@ -50,19 +52,20 @@ namespace SocketHttpListener.Net TextEncoding = textEncoding; MemoryStreamFactory = memoryStreamFactory; FileSystem = fileSystem; + EnvironmentInfo = environmentInfo; prefixes = new HttpListenerPrefixCollection(logger, this); registry = new Dictionary(); connections = new Dictionary(); auth_schemes = AuthenticationSchemes.Anonymous; } - public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem) - :this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem) + public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) + :this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo) { } - public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem) - : this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem) + public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo) + : this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo) { _certificate = certificate; } diff --git a/SocketHttpListener.Portable/Net/ResponseStream.cs b/SocketHttpListener.Portable/Net/ResponseStream.cs index dea4049d56..9552fe8ca0 100644 --- a/SocketHttpListener.Portable/Net/ResponseStream.cs +++ b/SocketHttpListener.Portable/Net/ResponseStream.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; @@ -28,8 +29,9 @@ namespace SocketHttpListener.Net private readonly IAcceptSocket _socket; private readonly bool _supportsDirectSocketAccess; private readonly ILogger _logger; + private readonly IEnvironmentInfo _environment; - internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IAcceptSocket socket, bool supportsDirectSocketAccess, ILogger logger) + internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IAcceptSocket socket, bool supportsDirectSocketAccess, ILogger logger, IEnvironmentInfo environment) { this.response = response; _memoryStreamFactory = memoryStreamFactory; @@ -38,6 +40,7 @@ namespace SocketHttpListener.Net _socket = socket; _supportsDirectSocketAccess = supportsDirectSocketAccess; _logger = logger; + _environment = environment; this.stream = stream; } @@ -344,7 +347,20 @@ namespace SocketHttpListener.Net private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) { - using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, true)) + var allowAsync = _environment.OperatingSystem != OperatingSystem.Windows; + + var fileOpenOptions = offset > 0 + ? FileOpenOptions.RandomAccess + : FileOpenOptions.SequentialScan; + + if (allowAsync) + { + fileOpenOptions |= FileOpenOptions.Asynchronous; + } + + // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + + using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions)) { if (offset > 0) { @@ -355,11 +371,53 @@ namespace SocketHttpListener.Net if (count > 0) { - await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false); + if (allowAsync) + { + await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false); + } + else + { + await CopyToInternalAsyncWithSyncRead(fs, targetStream, count, cancellationToken).ConfigureAwait(false); + } } else { - await fs.CopyToAsync(targetStream, 81920, cancellationToken).ConfigureAwait(false); + if (allowAsync) + { + await fs.CopyToAsync(targetStream, 81920, cancellationToken).ConfigureAwait(false); + } + else + { + fs.CopyTo(targetStream, 81920); + } + } + } + } + + private static async Task CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) + { + var array = new byte[81920]; + int bytesRead; + + while ((bytesRead = source.Read(array, 0, array.Length)) != 0) + { + if (bytesRead == 0) + { + break; + } + + var bytesToWrite = Math.Min(bytesRead, copyLength); + + if (bytesToWrite > 0) + { + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + } + + copyLength -= bytesToWrite; + + if (copyLength <= 0) + { + break; } } } @@ -368,7 +426,7 @@ namespace SocketHttpListener.Net { var array = new byte[81920]; int bytesRead; - + while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) { if (bytesRead == 0)