浏览代码

Merge pull request #3032 from MediaBrowser/beta

Beta
Luke 7 年之前
父节点
当前提交
8f78652398
共有 100 个文件被更改,包括 1192 次插入1344 次删除
  1. 41 12
      Emby.Dlna/ContentDirectory/ControlHandler.cs
  2. 0 1
      Emby.Dlna/DlnaManager.cs
  3. 0 2
      Emby.Dlna/Emby.Dlna.csproj
  4. 1 1
      Emby.Dlna/PlayTo/PlayToController.cs
  5. 4 2
      Emby.Dlna/Profiles/DefaultProfile.cs
  6. 5 5
      Emby.Dlna/Profiles/WdtvLiveProfile.cs
  7. 0 326
      Emby.Dlna/Profiles/Xbox360Profile.cs
  8. 2 2
      Emby.Dlna/Profiles/XboxOneProfile.cs
  9. 2 2
      Emby.Dlna/Profiles/Xml/Default.xml
  10. 5 5
      Emby.Dlna/Profiles/Xml/WDTV Live.xml
  11. 0 31
      Emby.Dlna/Profiles/Xml/Xbox 360.xml
  12. 2 2
      Emby.Dlna/Profiles/Xml/Xbox One.xml
  13. 2 2
      Emby.Drawing.Skia/Emby.Drawing.Skia.csproj
  14. 1 1
      Emby.Drawing.Skia/packages.config
  15. 3 2
      Emby.Server.Implementations/ApplicationHost.cs
  16. 2 14
      Emby.Server.Implementations/Channels/ChannelManager.cs
  17. 1 2
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  18. 3 5
      Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
  19. 0 240
      Emby.Server.Implementations/IO/ProgressStream.cs
  20. 31 11
      Emby.Server.Implementations/IO/SharpCifsFileSystem.cs
  21. 14 0
      Emby.Server.Implementations/Library/LibraryManager.cs
  22. 221 30
      Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
  23. 1 1
      Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
  24. 21 16
      Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  25. 0 30
      Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
  26. 24 4
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  27. 11 11
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  28. 93 21
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  29. 7 4
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
  30. 27 5
      Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
  31. 20 2
      Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
  32. 54 19
      Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
  33. 1 6
      Emby.Server.Implementations/Localization/Core/ar.json
  34. 43 48
      Emby.Server.Implementations/Localization/Core/bg-BG.json
  35. 1 6
      Emby.Server.Implementations/Localization/Core/ca.json
  36. 2 7
      Emby.Server.Implementations/Localization/Core/cs.json
  37. 1 6
      Emby.Server.Implementations/Localization/Core/da.json
  38. 1 6
      Emby.Server.Implementations/Localization/Core/de.json
  39. 1 6
      Emby.Server.Implementations/Localization/Core/en-GB.json
  40. 1 6
      Emby.Server.Implementations/Localization/Core/es-AR.json
  41. 2 7
      Emby.Server.Implementations/Localization/Core/es-MX.json
  42. 4 9
      Emby.Server.Implementations/Localization/Core/es.json
  43. 1 6
      Emby.Server.Implementations/Localization/Core/fr-CA.json
  44. 2 7
      Emby.Server.Implementations/Localization/Core/fr.json
  45. 1 6
      Emby.Server.Implementations/Localization/Core/he.json
  46. 1 6
      Emby.Server.Implementations/Localization/Core/hr.json
  47. 1 6
      Emby.Server.Implementations/Localization/Core/hu.json
  48. 1 6
      Emby.Server.Implementations/Localization/Core/it.json
  49. 1 6
      Emby.Server.Implementations/Localization/Core/kk.json
  50. 1 6
      Emby.Server.Implementations/Localization/Core/ko.json
  51. 1 6
      Emby.Server.Implementations/Localization/Core/lt-LT.json
  52. 1 6
      Emby.Server.Implementations/Localization/Core/ms.json
  53. 1 6
      Emby.Server.Implementations/Localization/Core/nb.json
  54. 1 6
      Emby.Server.Implementations/Localization/Core/nl.json
  55. 2 7
      Emby.Server.Implementations/Localization/Core/pl.json
  56. 1 6
      Emby.Server.Implementations/Localization/Core/pt-BR.json
  57. 1 6
      Emby.Server.Implementations/Localization/Core/pt-PT.json
  58. 1 6
      Emby.Server.Implementations/Localization/Core/ru.json
  59. 69 74
      Emby.Server.Implementations/Localization/Core/sk.json
  60. 1 6
      Emby.Server.Implementations/Localization/Core/sl-SI.json
  61. 2 7
      Emby.Server.Implementations/Localization/Core/sv.json
  62. 1 6
      Emby.Server.Implementations/Localization/Core/tr.json
  63. 1 6
      Emby.Server.Implementations/Localization/Core/zh-CN.json
  64. 1 6
      Emby.Server.Implementations/Localization/Core/zh-HK.json
  65. 20 8
      Emby.Server.Implementations/Localization/LocalizationManager.cs
  66. 5 4
      Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
  67. 11 1
      Emby.Server.Implementations/Net/SocketFactory.cs
  68. 13 7
      Emby.Server.Implementations/Networking/NetworkManager.cs
  69. 4 1
      Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
  70. 2 0
      Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  71. 12 0
      Emby.Server.Implementations/Session/HttpSessionController.cs
  72. 5 6
      Emby.Server.Implementations/Session/SessionManager.cs
  73. 10 5
      MediaBrowser.Api/Library/LibraryService.cs
  74. 2 31
      MediaBrowser.Api/Session/SessionsService.cs
  75. 34 1
      MediaBrowser.Api/StartupWizardService.cs
  76. 4 2
      MediaBrowser.Api/VideosService.cs
  77. 2 0
      MediaBrowser.Common/Net/HttpRequestOptions.cs
  78. 1 6
      MediaBrowser.Controller/Entities/BaseItem.cs
  79. 0 15
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  80. 11 1
      MediaBrowser.Controller/Entities/Folder.cs
  81. 27 51
      MediaBrowser.Controller/Entities/Video.cs
  82. 23 0
      MediaBrowser.Controller/IO/StreamHelper.cs
  83. 1 1
      MediaBrowser.Controller/LiveTv/ITunerHost.cs
  84. 9 7
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  85. 2 1
      MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs
  86. 0 3
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  87. 28 24
      MediaBrowser.Controller/Providers/DirectoryService.cs
  88. 3 0
      MediaBrowser.Controller/Providers/IDirectoryService.cs
  89. 85 19
      MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
  90. 13 1
      MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
  91. 0 1
      MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
  92. 6 12
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  93. 1 1
      MediaBrowser.Model/Dto/BaseItemDto.cs
  94. 77 5
      MediaBrowser.Model/Entities/MediaStream.cs
  95. 1 0
      MediaBrowser.Model/LiveTv/LiveTvOptions.cs
  96. 2 1
      MediaBrowser.Model/Session/GeneralCommandType.cs
  97. 9 1
      MediaBrowser.Model/Session/PlayRequest.cs
  98. 13 1
      MediaBrowser.Providers/Manager/ImageSaver.cs
  99. 5 3
      MediaBrowser.Providers/Manager/MetadataService.cs
  100. 3 3
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

+ 41 - 12
Emby.Dlna/ContentDirectory/ControlHandler.cs

@@ -491,18 +491,27 @@ namespace Emby.Dlna.ContentDirectory
                 return GetGenreItems(item, null, user, sort, startIndex, limit);
                 return GetGenreItems(item, null, user, sort, startIndex, limit);
             }
             }
 
 
-            var collectionFolder = item as ICollectionFolder;
-            if (collectionFolder != null && string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
+            if (!stubType.HasValue || stubType.Value != StubType.Folder)
             {
             {
-                return GetMusicFolders(item, user, stubType, sort, startIndex, limit);
-            }
-            if (collectionFolder != null && string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
-            {
-                return GetMovieFolders(item, user, stubType, sort, startIndex, limit);
-            }
-            if (collectionFolder != null && string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
-            {
-                return GetTvFolders(item, user, stubType, sort, startIndex, limit);
+                var collectionFolder = item as ICollectionFolder;
+                if (collectionFolder != null && string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
+                {
+                    return GetMusicFolders(item, user, stubType, sort, startIndex, limit);
+                }
+                if (collectionFolder != null && string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
+                {
+                    return GetMovieFolders(item, user, stubType, sort, startIndex, limit);
+                }
+                if (collectionFolder != null && string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
+                {
+                    return GetTvFolders(item, user, stubType, sort, startIndex, limit);
+                }
+
+                var userView = item as UserView;
+                if (userView != null && string.Equals(CollectionType.Folders, userView.ViewType, StringComparison.OrdinalIgnoreCase))
+                {
+                    return GetFolders(item, user, stubType, sort, startIndex, limit);
+                }
             }
             }
 
 
             if (stubType.HasValue)
             if (stubType.HasValue)
@@ -513,7 +522,10 @@ namespace Emby.Dlna.ContentDirectory
                     return GetItemsFromPerson(person, user, startIndex, limit);
                     return GetItemsFromPerson(person, user, startIndex, limit);
                 }
                 }
 
 
-                return ApplyPaging(new QueryResult<ServerItem>(), startIndex, limit);
+                if (stubType.Value != StubType.Folder)
+                {
+                    return ApplyPaging(new QueryResult<ServerItem>(), startIndex, limit);
+                }
             }
             }
 
 
             var folder = (Folder)item;
             var folder = (Folder)item;
@@ -733,6 +745,23 @@ namespace Emby.Dlna.ContentDirectory
             };
             };
         }
         }
 
 
+        private QueryResult<ServerItem> GetFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
+        {
+            var folders = user.RootFolder.GetChildren(user, true)
+                .OrderBy(i => i.SortName)
+                .Select(i => new ServerItem(i)
+                {
+                    StubType = StubType.Folder
+                })
+                .ToArray();
+
+            return new QueryResult<ServerItem>
+            {
+                Items = folders,
+                TotalRecordCount = folders.Length
+            };
+        }
+
         private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
         private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
         {
         {
             var query = new InternalItemsQuery(user)
             var query = new InternalItemsQuery(user)

+ 0 - 1
Emby.Dlna/DlnaManager.cs

@@ -554,7 +554,6 @@ namespace Emby.Dlna
             var list = new List<DeviceProfile>
             var list = new List<DeviceProfile>
             {
             {
                 new SamsungSmartTvProfile(),
                 new SamsungSmartTvProfile(),
-                new Xbox360Profile(),
                 new XboxOneProfile(),
                 new XboxOneProfile(),
                 new SonyPs3Profile(),
                 new SonyPs3Profile(),
                 new SonyPs4Profile(),
                 new SonyPs4Profile(),

+ 0 - 2
Emby.Dlna/Emby.Dlna.csproj

@@ -105,7 +105,6 @@
     <Compile Include="Profiles\SonyPs3Profile.cs" />
     <Compile Include="Profiles\SonyPs3Profile.cs" />
     <Compile Include="Profiles\SonyPs4Profile.cs" />
     <Compile Include="Profiles\SonyPs4Profile.cs" />
     <Compile Include="Profiles\WdtvLiveProfile.cs" />
     <Compile Include="Profiles\WdtvLiveProfile.cs" />
-    <Compile Include="Profiles\Xbox360Profile.cs" />
     <Compile Include="Profiles\XboxOneProfile.cs" />
     <Compile Include="Profiles\XboxOneProfile.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Server\DescriptionXmlBuilder.cs" />
     <Compile Include="Server\DescriptionXmlBuilder.cs" />
@@ -175,7 +174,6 @@
     <EmbeddedResource Include="Profiles\Xml\Sony PlayStation 3.xml" />
     <EmbeddedResource Include="Profiles\Xml\Sony PlayStation 3.xml" />
     <EmbeddedResource Include="Profiles\Xml\Sony PlayStation 4.xml" />
     <EmbeddedResource Include="Profiles\Xml\Sony PlayStation 4.xml" />
     <EmbeddedResource Include="Profiles\Xml\WDTV Live.xml" />
     <EmbeddedResource Include="Profiles\Xml\WDTV Live.xml" />
-    <EmbeddedResource Include="Profiles\Xml\Xbox 360.xml" />
     <EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
     <EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>

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

@@ -328,7 +328,7 @@ namespace Emby.Dlna.PlayTo
             {
             {
                 if (isFirst && command.StartPositionTicks.HasValue)
                 if (isFirst && command.StartPositionTicks.HasValue)
                 {
                 {
-                    playlist.Add(CreatePlaylistItem(item, user, command.StartPositionTicks.Value, null, null, null));
+                    playlist.Add(CreatePlaylistItem(item, user, command.StartPositionTicks.Value, command.MediaSourceId, command.AudioStreamIndex, command.SubtitleStreamIndex));
                     isFirst = false;
                     isFirst = false;
                 }
                 }
                 else
                 else

+ 4 - 2
Emby.Dlna/Profiles/DefaultProfile.cs

@@ -65,13 +65,15 @@ namespace Emby.Dlna.Profiles
             {
             {
                 new DirectPlayProfile
                 new DirectPlayProfile
                 {
                 {
-                    Container = "m4v,mpegts,ts,3gp,mov,xvid,vob,mkv,wmv,asf,ogm,ogv,m2v,avi,mpg,mpeg,mp4,webm,wtv,m2ts,dvr-ms",
+                    // play all
+                    Container = "",
                     Type = DlnaProfileType.Video
                     Type = DlnaProfileType.Video
                 },
                 },
 
 
                 new DirectPlayProfile
                 new DirectPlayProfile
                 {
                 {
-                    Container = "aac,mp3,mpa,wav,wma,mp2,ogg,oga,webma,ape,opus,flac,m4a",
+                    // play all
+                    Container = "",
                     Type = DlnaProfileType.Audio
                     Type = DlnaProfileType.Audio
                 }
                 }
             };
             };

+ 5 - 5
Emby.Dlna/Profiles/WdtvLiveProfile.cs

@@ -58,7 +58,7 @@ namespace Emby.Dlna.Profiles
                     Container = "avi",
                     Container = "avi",
                     Type = DlnaProfileType.Video,
                     Type = DlnaProfileType.Video,
                     VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
                     VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
-                    AudioCodec = "ac3,dca,mp2,mp3,pcm,dts"
+                    AudioCodec = "ac3,eac3,dca,mp2,mp3,pcm,dts"
                 },
                 },
 
 
                 new DirectPlayProfile
                 new DirectPlayProfile
@@ -66,7 +66,7 @@ namespace Emby.Dlna.Profiles
                     Container = "mpeg",
                     Container = "mpeg",
                     Type = DlnaProfileType.Video,
                     Type = DlnaProfileType.Video,
                     VideoCodec = "mpeg1video,mpeg2video",
                     VideoCodec = "mpeg1video,mpeg2video",
-                    AudioCodec = "ac3,dca,mp2,mp3,pcm,dts"
+                    AudioCodec = "ac3,eac3,dca,mp2,mp3,pcm,dts"
                 },
                 },
 
 
                 new DirectPlayProfile
                 new DirectPlayProfile
@@ -74,7 +74,7 @@ namespace Emby.Dlna.Profiles
                     Container = "mkv",
                     Container = "mkv",
                     Type = DlnaProfileType.Video,
                     Type = DlnaProfileType.Video,
                     VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
                     VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
-                    AudioCodec = "ac3,dca,aac,mp2,mp3,pcm,dts"
+                    AudioCodec = "ac3,eac3,dca,aac,mp2,mp3,pcm,dts"
                 },
                 },
 
 
                 new DirectPlayProfile
                 new DirectPlayProfile
@@ -82,7 +82,7 @@ namespace Emby.Dlna.Profiles
                     Container = "ts,m2ts",
                     Container = "ts,m2ts",
                     Type = DlnaProfileType.Video,
                     Type = DlnaProfileType.Video,
                     VideoCodec = "mpeg1video,mpeg2video,h264,vc1",
                     VideoCodec = "mpeg1video,mpeg2video,h264,vc1",
-                    AudioCodec = "ac3,dca,mp2,mp3,aac,dts"
+                    AudioCodec = "ac3,eac3,dca,mp2,mp3,aac,dts"
                 },
                 },
 
 
                 new DirectPlayProfile
                 new DirectPlayProfile
@@ -90,7 +90,7 @@ namespace Emby.Dlna.Profiles
                     Container = "mp4,mov,m4v",
                     Container = "mp4,mov,m4v",
                     Type = DlnaProfileType.Video,
                     Type = DlnaProfileType.Video,
                     VideoCodec = "h264,mpeg4",
                     VideoCodec = "h264,mpeg4",
-                    AudioCodec = "ac3,aac,mp2,mp3,dca,dts"
+                    AudioCodec = "ac3,eac3,aac,mp2,mp3,dca,dts"
                 },
                 },
 
 
                 new DirectPlayProfile
                 new DirectPlayProfile

+ 0 - 326
Emby.Dlna/Profiles/Xbox360Profile.cs

@@ -1,326 +0,0 @@
-using MediaBrowser.Model.Dlna;
-using System.Xml.Serialization;
-
-namespace Emby.Dlna.Profiles
-{
-    /// <summary>
-    /// Good info on xbox 360 requirements: https://code.google.com/p/jems/wiki/XBox360Notes
-    /// </summary>
-    [XmlRoot("Profile")]
-    public class Xbox360Profile : DefaultProfile
-    {
-        public Xbox360Profile()
-        {
-            Name = "Xbox 360";
-
-            // Required according to above
-            ModelName = "Windows Media Player Sharing";
-
-            ModelNumber = "12.0";
-
-            FriendlyName = "${HostName}: 1";
-
-            ModelUrl = "http://go.microsoft.com/fwlink/?LinkId=105926";
-            Manufacturer = "Microsoft Corporation";
-            ManufacturerUrl = "http://www.microsoft.com";
-            XDlnaDoc = "DMS-1.50";
-            ModelDescription = "Emby : UPnP Media Server";
-
-            TimelineOffsetSeconds = 40;
-            RequiresPlainFolders = true;
-            RequiresPlainVideoItems = true;
-            EnableMSMediaReceiverRegistrar = true;
-
-            Identification = new DeviceIdentification
-            {
-                ModelName = "Xbox 360",
-
-                Headers = new[]
-                {
-                    new HttpHeaderInfo {Name = "User-Agent", Value = "Xbox", Match = HeaderMatchType.Substring},
-                    new HttpHeaderInfo {Name = "User-Agent", Value = "Xenon", Match = HeaderMatchType.Substring}
-                }
-            };
-
-            TranscodingProfiles = new[]
-            {
-                new TranscodingProfile
-                {
-                    Container = "mp3",
-                    AudioCodec = "mp3",
-                    Type = DlnaProfileType.Audio
-                },
-                new TranscodingProfile
-                {
-                    Container = "asf",
-                    VideoCodec = "wmv2",
-                    AudioCodec = "wmav2",
-                    Type = DlnaProfileType.Video,
-                    TranscodeSeekInfo = TranscodeSeekInfo.Bytes,
-                    EstimateContentLength = true
-                },
-                new TranscodingProfile
-                {
-                    Container = "jpeg",
-                    Type = DlnaProfileType.Photo
-                }
-            };
-
-            DirectPlayProfiles = new[]
-            {
-                new DirectPlayProfile
-                {
-                    Container = "avi",
-                    VideoCodec = "mpeg4",
-                    AudioCodec = "ac3,mp3",
-                    Type = DlnaProfileType.Video
-                },
-                new DirectPlayProfile
-                {
-                    Container = "avi",
-                    VideoCodec = "h264",
-                    AudioCodec = "aac",
-                    Type = DlnaProfileType.Video
-                },
-                new DirectPlayProfile
-                {
-                    Container = "mp4,mov",
-                    VideoCodec = "h264,mpeg4",
-                    AudioCodec = "aac,ac3",
-                    Type = DlnaProfileType.Video
-                },
-                new DirectPlayProfile
-                {
-                    Container = "asf",
-                    VideoCodec = "wmv2,wmv3,vc1",
-                    AudioCodec = "wmav2,wmapro",
-                    Type = DlnaProfileType.Video
-                },
-                new DirectPlayProfile
-                {
-                    Container = "asf",
-                    AudioCodec = "wmav2,wmapro,wmavoice",
-                    Type = DlnaProfileType.Audio
-                },
-                new DirectPlayProfile
-                {
-                    Container = "mp3",
-                    AudioCodec = "mp3",
-                    Type = DlnaProfileType.Audio
-                },
-                new DirectPlayProfile
-                {
-                    Container = "jpeg",
-                    Type = DlnaProfileType.Photo
-                }
-            };
-
-            ResponseProfiles = new[]
-            {
-                new ResponseProfile
-                {
-                    Container = "avi",
-                    MimeType = "video/avi",
-                    Type = DlnaProfileType.Video
-                }
-            };
-
-            ContainerProfiles = new[]
-            {
-                new ContainerProfile
-                {
-                    Type = DlnaProfileType.Video,
-                    Container = "mp4,mov",
-
-                    Conditions = new []
-                    {
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.Equals,
-                            Property = ProfileConditionValue.Has64BitOffsets,
-                            Value = "false",
-                            IsRequired = false
-                        }
-                    }
-                },
-
-                new ContainerProfile
-                {
-                    Type = DlnaProfileType.Photo,
-
-                    Conditions = new []
-                    {
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.Width,
-                            Value = "1920"
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.Height,
-                            Value = "1080"
-                        }
-                    }
-                }
-            };
-
-            CodecProfiles = new[]
-            {
-                new CodecProfile
-                {
-                    Type = CodecType.Video,
-                    Codec = "mpeg4",
-                    Conditions = new []
-                    {
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.Width,
-                            Value = "1280"
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.Height,
-                            Value = "720"
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.VideoFramerate,
-                            Value = "30",
-                            IsRequired = false
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.VideoBitrate,
-                            Value = "5120000",
-                            IsRequired = false
-                        }
-                    }
-                },
-
-                new CodecProfile
-                {
-                    Type = CodecType.Video,
-                    Codec = "h264",
-                    Conditions = new []
-                    {
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.Width,
-                            Value = "1920"
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.Height,
-                            Value = "1080"
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.VideoLevel,
-                            Value = "41",
-                            IsRequired = false
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.VideoBitrate,
-                            Value = "10240000",
-                            IsRequired = false
-                        }
-                    }
-                },
-
-                new CodecProfile
-                {
-                    Type = CodecType.Video,
-                    Codec = "wmv2,wmv3,vc1",
-                    Conditions = new []
-                    {
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.Width,
-                            Value = "1920"
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.Height,
-                            Value = "1080"
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.VideoFramerate,
-                            Value = "30",
-                            IsRequired = false
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.VideoBitrate,
-                            Value = "15360000",
-                            IsRequired = false
-                        }
-                    }
-                },
-
-                new CodecProfile
-                {
-                    Type = CodecType.VideoAudio,
-                    Codec = "ac3,wmav2,wmapro",
-                    Conditions = new []
-                    {
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.AudioChannels,
-                            Value = "6",
-                            IsRequired = false
-                        }
-                    }
-                },
-
-                new CodecProfile
-                {
-                    Type = CodecType.VideoAudio,
-                    Codec = "aac",
-                    Conditions = new []
-                    {
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.LessThanEqual,
-                            Property = ProfileConditionValue.AudioChannels,
-                            Value = "2",
-                            IsRequired = false
-                        },
-                        new ProfileCondition
-                        {
-                            Condition = ProfileConditionType.Equals,
-                            Property = ProfileConditionValue.AudioProfile,
-                            Value = "lc",
-                            IsRequired = false
-                        }
-                    }
-                }
-            };
-
-            SubtitleProfiles = new[]
-            {
-                new SubtitleProfile
-                {
-                    Format = "srt",
-                    Method = SubtitleDeliveryMethod.Embed
-                }
-            };
-        }
-    }
-}

+ 2 - 2
Emby.Dlna/Profiles/XboxOneProfile.cs

@@ -60,7 +60,7 @@ namespace Emby.Dlna.Profiles
                 new DirectPlayProfile
                 new DirectPlayProfile
                 {
                 {
                     Container = "ts",
                     Container = "ts",
-                    VideoCodec = "h264,mpeg2video",
+                    VideoCodec = "h264,mpeg2video,hevc",
                     AudioCodec = "ac3,aac,mp3",
                     AudioCodec = "ac3,aac,mp3",
                     Type = DlnaProfileType.Video
                     Type = DlnaProfileType.Video
                 },
                 },
@@ -81,7 +81,7 @@ namespace Emby.Dlna.Profiles
                 new DirectPlayProfile
                 new DirectPlayProfile
                 {
                 {
                     Container = "mp4,mov,mkv,m4v",
                     Container = "mp4,mov,mkv,m4v",
-                    VideoCodec = "h264,mpeg4,mpeg2video",
+                    VideoCodec = "h264,mpeg4,mpeg2video,hevc",
                     AudioCodec = "aac,ac3",
                     AudioCodec = "aac,ac3",
                     Type = DlnaProfileType.Video
                     Type = DlnaProfileType.Video
                 },
                 },

+ 2 - 2
Emby.Dlna/Profiles/Xml/Default.xml

@@ -29,8 +29,8 @@
   <IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
   <IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
   <XmlRootAttributes />
   <XmlRootAttributes />
   <DirectPlayProfiles>
   <DirectPlayProfiles>
-    <DirectPlayProfile container="m4v,mpegts,ts,3gp,mov,xvid,vob,mkv,wmv,asf,ogm,ogv,m2v,avi,mpg,mpeg,mp4,webm,wtv,m2ts,dvr-ms" type="Video" />
-    <DirectPlayProfile container="aac,mp3,mpa,wav,wma,mp2,ogg,oga,webma,ape,opus,flac,m4a" type="Audio" />
+    <DirectPlayProfile container="" type="Video" />
+    <DirectPlayProfile container="" type="Audio" />
   </DirectPlayProfiles>
   </DirectPlayProfiles>
   <TranscodingProfiles>
   <TranscodingProfiles>
     <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />
     <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" enableSubtitlesInManifest="false" minSegments="0" segmentLength="0" breakOnNonKeyFrames="false" />

+ 5 - 5
Emby.Dlna/Profiles/Xml/WDTV Live.xml

@@ -36,11 +36,11 @@
   <IgnoreTranscodeByteRangeRequests>true</IgnoreTranscodeByteRangeRequests>
   <IgnoreTranscodeByteRangeRequests>true</IgnoreTranscodeByteRangeRequests>
   <XmlRootAttributes />
   <XmlRootAttributes />
   <DirectPlayProfiles>
   <DirectPlayProfiles>
-    <DirectPlayProfile container="avi" audioCodec="ac3,dca,mp2,mp3,pcm,dts" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
-    <DirectPlayProfile container="mpeg" audioCodec="ac3,dca,mp2,mp3,pcm,dts" videoCodec="mpeg1video,mpeg2video" type="Video" />
-    <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp2,mp3,pcm,dts" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
-    <DirectPlayProfile container="ts,m2ts" audioCodec="ac3,dca,mp2,mp3,aac,dts" videoCodec="mpeg1video,mpeg2video,h264,vc1" type="Video" />
-    <DirectPlayProfile container="mp4,mov,m4v" audioCodec="ac3,aac,mp2,mp3,dca,dts" videoCodec="h264,mpeg4" type="Video" />
+    <DirectPlayProfile container="avi" audioCodec="ac3,eac3,dca,mp2,mp3,pcm,dts" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
+    <DirectPlayProfile container="mpeg" audioCodec="ac3,eac3,dca,mp2,mp3,pcm,dts" videoCodec="mpeg1video,mpeg2video" type="Video" />
+    <DirectPlayProfile container="mkv" audioCodec="ac3,eac3,dca,aac,mp2,mp3,pcm,dts" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
+    <DirectPlayProfile container="ts,m2ts" audioCodec="ac3,eac3,dca,mp2,mp3,aac,dts" videoCodec="mpeg1video,mpeg2video,h264,vc1" type="Video" />
+    <DirectPlayProfile container="mp4,mov,m4v" audioCodec="ac3,eac3,aac,mp2,mp3,dca,dts" videoCodec="h264,mpeg4" type="Video" />
     <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="vc1" type="Video" />
     <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="vc1" type="Video" />
     <DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" />
     <DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" />
     <DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" />
     <DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" />

文件差异内容过多而无法显示
+ 0 - 31
Emby.Dlna/Profiles/Xml/Xbox 360.xml


+ 2 - 2
Emby.Dlna/Profiles/Xml/Xbox One.xml

@@ -36,10 +36,10 @@
   <IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
   <IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
   <XmlRootAttributes />
   <XmlRootAttributes />
   <DirectPlayProfiles>
   <DirectPlayProfiles>
-    <DirectPlayProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264,mpeg2video" type="Video" />
+    <DirectPlayProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264,mpeg2video,hevc" type="Video" />
     <DirectPlayProfile container="avi" audioCodec="ac3,mp3" videoCodec="mpeg4" type="Video" />
     <DirectPlayProfile container="avi" audioCodec="ac3,mp3" videoCodec="mpeg4" type="Video" />
     <DirectPlayProfile container="avi" audioCodec="aac" videoCodec="h264" type="Video" />
     <DirectPlayProfile container="avi" audioCodec="aac" videoCodec="h264" type="Video" />
-    <DirectPlayProfile container="mp4,mov,mkv,m4v" audioCodec="aac,ac3" videoCodec="h264,mpeg4,mpeg2video" type="Video" />
+    <DirectPlayProfile container="mp4,mov,mkv,m4v" audioCodec="aac,ac3" videoCodec="h264,mpeg4,mpeg2video,hevc" type="Video" />
     <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="wmv2,wmv3,vc1" type="Video" />
     <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="wmv2,wmv3,vc1" type="Video" />
     <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" />
     <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" />
     <DirectPlayProfile container="mp3" audioCodec="mp3" type="Audio" />
     <DirectPlayProfile container="mp3" audioCodec="mp3" type="Audio" />

+ 2 - 2
Emby.Drawing.Skia/Emby.Drawing.Skia.csproj

@@ -63,8 +63,8 @@
     <EmbeddedResource Include="fonts\robotoregular.ttf" />
     <EmbeddedResource Include="fonts\robotoregular.ttf" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
-    <Reference Include="SkiaSharp, Version=1.59.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
-      <HintPath>..\packages\SkiaSharp.1.59.2\lib\portable-net45+win8+wpa81+wp8\SkiaSharp.dll</HintPath>
+    <Reference Include="SkiaSharp, Version=1.58.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
+      <HintPath>..\packages\SkiaSharp.1.58.1\lib\portable-net45+win8+wpa81+wp8\SkiaSharp.dll</HintPath>
     </Reference>
     </Reference>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>

+ 1 - 1
Emby.Drawing.Skia/packages.config

@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
 <packages>
-  <package id="SkiaSharp" version="1.59.2" targetFramework="portable45-net45+win8" />
+  <package id="SkiaSharp" version="1.58.1" targetFramework="portable45-net45+win8" />
 </packages>
 </packages>

+ 3 - 2
Emby.Server.Implementations/ApplicationHost.cs

@@ -799,7 +799,7 @@ namespace Emby.Server.Implementations
 
 
         protected abstract IConnectManager CreateConnectManager();
         protected abstract IConnectManager CreateConnectManager();
         protected abstract ISyncManager CreateSyncManager();
         protected abstract ISyncManager CreateSyncManager();
-        
+
         protected virtual IHttpClient CreateHttpClient()
         protected virtual IHttpClient CreateHttpClient()
         {
         {
             return new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamFactory, GetDefaultUserAgent);
             return new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager, MemoryStreamFactory, GetDefaultUserAgent);
@@ -953,6 +953,7 @@ namespace Emby.Server.Implementations
             var deviceRepo = new SqliteDeviceRepository(LogManager.GetLogger("DeviceManager"), ServerConfigurationManager, FileSystemManager, JsonSerializer);
             var deviceRepo = new SqliteDeviceRepository(LogManager.GetLogger("DeviceManager"), ServerConfigurationManager, FileSystemManager, JsonSerializer);
             deviceRepo.Initialize();
             deviceRepo.Initialize();
             DeviceManager = new DeviceManager(deviceRepo, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LogManager.GetLogger("DeviceManager"), NetworkManager);
             DeviceManager = new DeviceManager(deviceRepo, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LogManager.GetLogger("DeviceManager"), NetworkManager);
+            RegisterSingleInstance<IDeviceRepository>(deviceRepo);
             RegisterSingleInstance(DeviceManager);
             RegisterSingleInstance(DeviceManager);
 
 
             var newsService = new Emby.Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer);
             var newsService = new Emby.Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer);
@@ -996,7 +997,7 @@ namespace Emby.Server.Implementations
             NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
             NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
             RegisterSingleInstance(NotificationManager);
             RegisterSingleInstance(NotificationManager);
 
 
-            SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager);
+            SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager, ServerConfigurationManager);
             RegisterSingleInstance(SubtitleManager);
             RegisterSingleInstance(SubtitleManager);
 
 
             RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, SocketFactory, TimerFactory));
             RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, SocketFactory, TimerFactory));

+ 2 - 14
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -296,7 +296,7 @@ namespace Emby.Server.Implementations.Channels
 
 
             if (requiresCallback != null)
             if (requiresCallback != null)
             {
             {
-                results = await GetChannelItemMediaSourcesInternal(requiresCallback, GetItemExternalId(item), cancellationToken)
+                results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
                     .ConfigureAwait(false);
                     .ConfigureAwait(false);
             }
             }
             else
             else
@@ -990,18 +990,6 @@ namespace Emby.Server.Implementations.Channels
             return result;
             return result;
         }
         }
 
 
-        private string GetItemExternalId(BaseItem item)
-        {
-            var externalId = item.ExternalId;
-
-            if (string.IsNullOrWhiteSpace(externalId))
-            {
-                externalId = item.GetProviderId("ProviderExternalId");
-            }
-
-            return externalId;
-        }
-
         private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
         private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
         private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
         private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
             User user,
             User user,
@@ -1080,7 +1068,7 @@ namespace Emby.Server.Implementations.Channels
                 {
                 {
                     var categoryItem = _libraryManager.GetItemById(new Guid(folderId));
                     var categoryItem = _libraryManager.GetItemById(new Guid(folderId));
 
 
-                    query.FolderId = GetItemExternalId(categoryItem);
+                    query.FolderId = categoryItem.ExternalId;
                 }
                 }
 
 
                 var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false);
                 var result = await channel.GetChannelItems(query, cancellationToken).ConfigureAwait(false);

+ 1 - 2
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -117,7 +117,6 @@
     <Compile Include="IO\ManagedFileSystem.cs" />
     <Compile Include="IO\ManagedFileSystem.cs" />
     <Compile Include="IO\MbLinkShortcutHandler.cs" />
     <Compile Include="IO\MbLinkShortcutHandler.cs" />
     <Compile Include="IO\MemoryStreamProvider.cs" />
     <Compile Include="IO\MemoryStreamProvider.cs" />
-    <Compile Include="IO\ProgressStream.cs" />
     <Compile Include="IO\SharpCifsFileSystem.cs" />
     <Compile Include="IO\SharpCifsFileSystem.cs" />
     <Compile Include="IO\SharpCifs\Config.cs" />
     <Compile Include="IO\SharpCifs\Config.cs" />
     <Compile Include="IO\SharpCifs\Dcerpc\DcerpcBind.cs" />
     <Compile Include="IO\SharpCifs\Dcerpc\DcerpcBind.cs" />
@@ -405,11 +404,11 @@
     <Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
     <Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunManager.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunManager.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
-    <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHttpStream.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunUdpStream.cs" />
     <Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunUdpStream.cs" />
     <Compile Include="LiveTv\TunerHosts\LiveStream.cs" />
     <Compile Include="LiveTv\TunerHosts\LiveStream.cs" />
     <Compile Include="LiveTv\TunerHosts\M3uParser.cs" />
     <Compile Include="LiveTv\TunerHosts\M3uParser.cs" />
     <Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" />
     <Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" />
+    <Compile Include="LiveTv\TunerHosts\SharedHttpStream.cs" />
     <Compile Include="Localization\LocalizationManager.cs" />
     <Compile Include="Localization\LocalizationManager.cs" />
     <Compile Include="Localization\TextLocalizer.cs" />
     <Compile Include="Localization\TextLocalizer.cs" />
     <Compile Include="Logging\ConsoleLogger.cs" />
     <Compile Include="Logging\ConsoleLogger.cs" />

+ 3 - 5
Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs

@@ -16,6 +16,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
+using MediaBrowser.Controller.IO;
 
 
 namespace Emby.Server.Implementations.HttpClientManager
 namespace Emby.Server.Implementations.HttpClientManager
 {
 {
@@ -633,12 +634,9 @@ namespace Emby.Server.Implementations.HttpClientManager
                     }
                     }
                     else
                     else
                     {
                     {
-                        using (var stream = ProgressStream.CreateReadProgressStream(httpResponse.GetResponseStream(), options.Progress.Report, contentLength.Value))
+                        using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
                         {
                         {
-                            using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
-                            {
-                                await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
-                            }
+                            await StreamHelper.CopyToAsync(httpResponse.GetResponseStream(), fs, StreamDefaults.DefaultCopyToBufferSize, options.Progress, contentLength.Value, options.CancellationToken).ConfigureAwait(false);
                         }
                         }
                     }
                     }
 
 

+ 0 - 240
Emby.Server.Implementations/IO/ProgressStream.cs

@@ -1,240 +0,0 @@
-using System;
-using System.IO;
-
-namespace Emby.Server.Implementations.IO
-{
-    /// <summary>
-    /// Measures progress when reading from a stream or writing to one
-    /// </summary>
-    public class ProgressStream : Stream
-    {
-        /// <summary>
-        /// Gets the base stream.
-        /// </summary>
-        /// <value>The base stream.</value>
-        public Stream BaseStream { get; private set; }
-
-        /// <summary>
-        /// Gets or sets the bytes processed.
-        /// </summary>
-        /// <value>The bytes processed.</value>
-        private long BytesProcessed { get; set; }
-        /// <summary>
-        /// Gets or sets the length of the write.
-        /// </summary>
-        /// <value>The length of the write.</value>
-        private long WriteLength { get; set; }
-
-        /// <summary>
-        /// Gets or sets the length of the read.
-        /// </summary>
-        /// <value>The length of the read.</value>
-        private long? ReadLength { get; set; }
-
-        /// <summary>
-        /// Gets or sets the progress action.
-        /// </summary>
-        /// <value>The progress action.</value>
-        private Action<double> ProgressAction { get; set; }
-
-        /// <summary>
-        /// Creates the read progress stream.
-        /// </summary>
-        /// <param name="baseStream">The base stream.</param>
-        /// <param name="progressAction">The progress action.</param>
-        /// <param name="readLength">Length of the read.</param>
-        /// <returns>ProgressStream.</returns>
-        public static ProgressStream CreateReadProgressStream(Stream baseStream, Action<double> progressAction, long? readLength = null)
-        {
-            return new ProgressStream
-            {
-                BaseStream = baseStream,
-                ProgressAction = progressAction,
-                ReadLength = readLength
-            };
-        }
-
-        /// <summary>
-        /// Creates the write progress stream.
-        /// </summary>
-        /// <param name="baseStream">The base stream.</param>
-        /// <param name="progressAction">The progress action.</param>
-        /// <param name="writeLength">Length of the write.</param>
-        /// <returns>ProgressStream.</returns>
-        public static ProgressStream CreateWriteProgressStream(Stream baseStream, Action<double> progressAction, long writeLength)
-        {
-            return new ProgressStream
-            {
-                BaseStream = baseStream,
-                ProgressAction = progressAction,
-                WriteLength = writeLength
-            };
-        }
-
-        /// <summary>
-        /// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
-        /// </summary>
-        /// <value><c>true</c> if this instance can read; otherwise, <c>false</c>.</value>
-        /// <returns>true if the stream supports reading; otherwise, false.</returns>
-        public override bool CanRead
-        {
-            get { return BaseStream.CanRead; }
-        }
-
-        /// <summary>
-        /// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
-        /// </summary>
-        /// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
-        /// <returns>true if the stream supports seeking; otherwise, false.</returns>
-        public override bool CanSeek
-        {
-            get { return BaseStream.CanSeek; }
-        }
-
-        /// <summary>
-        /// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
-        /// </summary>
-        /// <value><c>true</c> if this instance can write; otherwise, <c>false</c>.</value>
-        /// <returns>true if the stream supports writing; otherwise, false.</returns>
-        public override bool CanWrite
-        {
-            get { return BaseStream.CanWrite; }
-        }
-
-        /// <summary>
-        /// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device.
-        /// </summary>
-        public override void Flush()
-        {
-            BaseStream.Flush();
-        }
-
-        /// <summary>
-        /// When overridden in a derived class, gets the length in bytes of the stream.
-        /// </summary>
-        /// <value>The length.</value>
-        /// <returns>A long value representing the length of the stream in bytes.</returns>
-        public override long Length
-        {
-            get { return BaseStream.Length; }
-        }
-
-        /// <summary>
-        /// When overridden in a derived class, gets or sets the position within the current stream.
-        /// </summary>
-        /// <value>The position.</value>
-        /// <returns>The current position within the stream.</returns>
-        public override long Position
-        {
-            get { return BaseStream.Position; }
-            set
-            {
-                BaseStream.Position = value;
-            }
-        }
-
-        /// <summary>
-        /// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
-        /// </summary>
-        /// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param>
-        /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param>
-        /// <param name="count">The maximum number of bytes to be read from the current stream.</param>
-        /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.</returns>
-        public override int Read(byte[] buffer, int offset, int count)
-        {
-            var read = BaseStream.Read(buffer, offset, count);
-
-            BytesProcessed += read;
-
-            double percent = BytesProcessed;
-            percent /= ReadLength ?? BaseStream.Length;
-            percent *= 100;
-
-            ProgressAction(percent);
-
-            return read;
-        }
-
-        public override int EndRead(IAsyncResult asyncResult)
-        {
-            var read = base.EndRead(asyncResult);
-
-            BytesProcessed += read;
-
-            double percent = BytesProcessed;
-            percent /= ReadLength ?? BaseStream.Length;
-            percent *= 100;
-
-            ProgressAction(percent);
-
-            return read;
-        }
-
-        /// <summary>
-        /// When overridden in a derived class, sets the position within the current stream.
-        /// </summary>
-        /// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param>
-        /// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param>
-        /// <returns>The new position within the current stream.</returns>
-        public override long Seek(long offset, SeekOrigin origin)
-        {
-            return BaseStream.Seek(offset, origin);
-        }
-
-        /// <summary>
-        /// When overridden in a derived class, sets the length of the current stream.
-        /// </summary>
-        /// <param name="value">The desired length of the current stream in bytes.</param>
-        public override void SetLength(long value)
-        {
-            BaseStream.SetLength(value);
-        }
-
-        /// <summary>
-        /// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
-        /// </summary>
-        /// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param>
-        /// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param>
-        /// <param name="count">The number of bytes to be written to the current stream.</param>
-        public override void Write(byte[] buffer, int offset, int count)
-        {
-            BaseStream.Write(buffer, offset, count);
-
-            BytesProcessed += count;
-
-            double percent = BytesProcessed;
-            percent /= WriteLength;
-            percent *= 100;
-
-            ProgressAction(percent);
-        }
-
-        public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
-        {
-            var result = base.BeginWrite(buffer, offset, count, callback, state);
-
-            BytesProcessed += count;
-
-            double percent = BytesProcessed;
-            percent /= WriteLength;
-            percent *= 100;
-
-            ProgressAction(percent);
-
-            return result;
-        }
-
-        /// <summary>
-        /// Releases the unmanaged resources used by the <see cref="T:System.IO.Stream" /> and optionally releases the managed resources.
-        /// </summary>
-        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
-        protected override void Dispose(bool disposing)
-        {
-            if (disposing)
-            {
-                BaseStream.Dispose();
-            }
-            base.Dispose(disposing);
-        }
-    }
-}

+ 31 - 11
Emby.Server.Implementations/IO/SharpCifsFileSystem.cs

@@ -487,14 +487,17 @@ namespace Emby.Server.Implementations.IO
             AssertDirectoryExists(dir, path);
             AssertDirectoryExists(dir, path);
 
 
             var list = ListFiles(dir, recursive);
             var list = ListFiles(dir, recursive);
+            var result = new List<FileSystemMetadata>();
 
 
             foreach (var file in list)
             foreach (var file in list)
             {
             {
                 if (file.IsDirectory())
                 if (file.IsDirectory())
                 {
                 {
-                    yield return ToMetadata(file);
+                    result.Add(ToMetadata(file));
                 }
                 }
             }
             }
+
+            return result;
         }
         }
 
 
         public IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
         public IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
@@ -503,6 +506,7 @@ namespace Emby.Server.Implementations.IO
             AssertDirectoryExists(dir, path);
             AssertDirectoryExists(dir, path);
 
 
             var list = ListFiles(dir, recursive);
             var list = ListFiles(dir, recursive);
+            var result = new List<FileSystemMetadata>();
 
 
             foreach (var file in list)
             foreach (var file in list)
             {
             {
@@ -513,10 +517,12 @@ namespace Emby.Server.Implementations.IO
 
 
                     if (extensions == null || extensions.Length == 0 || extensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
                     if (extensions == null || extensions.Length == 0 || extensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
                     {
                     {
-                        yield return ToMetadata(file);
+                        result.Add(ToMetadata(file));
                     }
                     }
                 }
                 }
             }
             }
+
+            return result;
         }
         }
 
 
         public IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
         public IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
@@ -525,15 +531,19 @@ namespace Emby.Server.Implementations.IO
             AssertDirectoryExists(dir, path);
             AssertDirectoryExists(dir, path);
 
 
             var list = ListFiles(dir, recursive);
             var list = ListFiles(dir, recursive);
+            var result = new List<FileSystemMetadata>();
 
 
             foreach (var file in list)
             foreach (var file in list)
             {
             {
-                yield return ToMetadata(file);
+                result.Add(ToMetadata(file));
             }
             }
+
+            return result;
         }
         }
 
 
-        public IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
+        public List<string> GetFileSystemEntryPaths(string path, bool recursive = false)
         {
         {
+            var result = new List<string>();
             var dir = CreateSmbDirectoryForListFiles(path);
             var dir = CreateSmbDirectoryForListFiles(path);
             AssertDirectoryExists(dir, path);
             AssertDirectoryExists(dir, path);
 
 
@@ -541,16 +551,18 @@ namespace Emby.Server.Implementations.IO
 
 
             foreach (var file in list)
             foreach (var file in list)
             {
             {
-                yield return GetReturnPath(file);
+                result.Add(GetReturnPath(file));
             }
             }
+            return result;
         }
         }
 
 
-        public IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
+        public List<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
         {
         {
             var dir = CreateSmbDirectoryForListFiles(path);
             var dir = CreateSmbDirectoryForListFiles(path);
             AssertDirectoryExists(dir, path);
             AssertDirectoryExists(dir, path);
 
 
             var list = ListFiles(dir, recursive);
             var list = ListFiles(dir, recursive);
+            var result = new List<string>();
 
 
             foreach (var file in list)
             foreach (var file in list)
             {
             {
@@ -561,44 +573,52 @@ namespace Emby.Server.Implementations.IO
 
 
                     if (extensions == null || extensions.Length == 0 || extensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
                     if (extensions == null || extensions.Length == 0 || extensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
                     {
                     {
-                        yield return filePath;
+                        result.Add(filePath);
                     }
                     }
                 }
                 }
             }
             }
+
+            return result;
         }
         }
 
 
-        public IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
+        public List<string> GetDirectoryPaths(string path, bool recursive = false)
         {
         {
             var dir = CreateSmbDirectoryForListFiles(path);
             var dir = CreateSmbDirectoryForListFiles(path);
             AssertDirectoryExists(dir, path);
             AssertDirectoryExists(dir, path);
 
 
             var list = ListFiles(dir, recursive);
             var list = ListFiles(dir, recursive);
+            var result = new List<string>();
 
 
             foreach (var file in list)
             foreach (var file in list)
             {
             {
                 if (file.IsDirectory())
                 if (file.IsDirectory())
                 {
                 {
-                    yield return GetReturnPath(file);
+                    result.Add(GetReturnPath(file));
                 }
                 }
             }
             }
+
+            return result;
         }
         }
 
 
         private IEnumerable<SmbFile> ListFiles(SmbFile dir, bool recursive)
         private IEnumerable<SmbFile> ListFiles(SmbFile dir, bool recursive)
         {
         {
             var list = dir.ListFiles();
             var list = dir.ListFiles();
+            var result = new List<SmbFile>();
 
 
             foreach (var file in list)
             foreach (var file in list)
             {
             {
-                yield return file;
+                result.Add(file);
 
 
                 if (recursive && file.IsDirectory())
                 if (recursive && file.IsDirectory())
                 {
                 {
                     foreach (var subFile in ListFiles(file, recursive))
                     foreach (var subFile in ListFiles(file, recursive))
                     {
                     {
-                        yield return subFile;
+                        result.Add(subFile);
                     }
                     }
                 }
                 }
             }
             }
+
+            return result;
         }
         }
     }
     }
 }
 }

+ 14 - 0
Emby.Server.Implementations/Library/LibraryManager.cs

@@ -709,6 +709,13 @@ namespace Emby.Server.Implementations.Library
 
 
             var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? (AggregateFolder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath));
             var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? (AggregateFolder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath));
 
 
+            // In case program data folder was moved
+            if (!string.Equals(rootFolder.Path, rootFolderPath, StringComparison.Ordinal))
+            {
+                _logger.Info("Resetting root folder path to {0}", rootFolderPath);
+                rootFolder.Path = rootFolderPath;
+            }
+
             // Add in the plug-in folders
             // Add in the plug-in folders
             foreach (var child in PluginFolderCreators)
             foreach (var child in PluginFolderCreators)
             {
             {
@@ -771,6 +778,13 @@ namespace Emby.Server.Implementations.Library
                             tmpItem = (UserRootFolder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath));
                             tmpItem = (UserRootFolder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath));
                         }
                         }
 
 
+                        // In case program data folder was moved
+                        if (!string.Equals(tmpItem.Path, userRootPath, StringComparison.Ordinal))
+                        {
+                            _logger.Info("Resetting user root folder path to {0}", userRootPath);
+                            tmpItem.Path = userRootPath;
+                        }
+
                         _userRootFolder = tmpItem;
                         _userRootFolder = tmpItem;
                     }
                     }
                 }
                 }

+ 221 - 30
Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs

@@ -4,19 +4,26 @@ using MediaBrowser.Model.Entities;
 using System;
 using System;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using System.IO;
 using System.IO;
+using System.Linq;
+using MediaBrowser.Controller.Providers;
+using System.Collections.Generic;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Extensions;
+using Emby.Naming.Video;
+using Emby.Naming.AudioBook;
 
 
 namespace Emby.Server.Implementations.Library.Resolvers.Audio
 namespace Emby.Server.Implementations.Library.Resolvers.Audio
 {
 {
     /// <summary>
     /// <summary>
     /// Class AudioResolver
     /// Class AudioResolver
     /// </summary>
     /// </summary>
-    public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>
+    public class AudioResolver : ItemResolver<MediaBrowser.Controller.Entities.Audio.Audio>, IMultiItemResolver
     {
     {
-        private readonly ILibraryManager _libraryManager;
+        private readonly ILibraryManager LibraryManager;
 
 
         public AudioResolver(ILibraryManager libraryManager)
         public AudioResolver(ILibraryManager libraryManager)
         {
         {
-            _libraryManager = libraryManager;
+            LibraryManager = libraryManager;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -25,7 +32,38 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
         /// <value>The priority.</value>
         /// <value>The priority.</value>
         public override ResolverPriority Priority
         public override ResolverPriority Priority
         {
         {
-            get { return ResolverPriority.Last; }
+            get { return ResolverPriority.Fourth; }
+        }
+
+        public MultiItemResolverResult ResolveMultiple(Folder parent,
+            List<FileSystemMetadata> files,
+            string collectionType,
+            IDirectoryService directoryService)
+        {
+            var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
+
+            if (result != null)
+            {
+                foreach (var item in result.Items)
+                {
+                    SetInitialItemValues((MediaBrowser.Controller.Entities.Audio.Audio)item, null);
+                }
+            }
+
+            return result;
+        }
+
+        private MultiItemResolverResult ResolveMultipleInternal(Folder parent,
+            List<FileSystemMetadata> files,
+            string collectionType,
+            IDirectoryService directoryService)
+        {
+            if (string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
+            {
+                return ResolveMultipleAudio<AudioBook>(parent, files, directoryService, false, collectionType, true);
+            }
+
+            return null;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -37,46 +75,199 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
         {
         {
             // Return audio if the path is a file and has a matching extension
             // Return audio if the path is a file and has a matching extension
 
 
-            if (!args.IsDirectory)
+            var libraryOptions = args.GetLibraryOptions();
+            var collectionType = args.GetCollectionType();
+
+            var isBooksCollectionType = string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase);
+
+            if (args.IsDirectory)
             {
             {
-                var libraryOptions = args.GetLibraryOptions();
+                if (!isBooksCollectionType)
+                {
+                    return null;
+                }
+
+                var files = args.FileSystemChildren
+                    .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
+                    .ToList();
 
 
-                if (_libraryManager.IsAudioFile(args.Path, libraryOptions))
+                if (isBooksCollectionType)
                 {
                 {
-                    if (string.Equals(Path.GetExtension(args.Path), ".cue", StringComparison.OrdinalIgnoreCase))
-                    {
-                        // if audio file exists of same name, return null
+                    return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
+                }
+
+                return null;
+            }
+
+            if (LibraryManager.IsAudioFile(args.Path, libraryOptions))
+            {
+                if (string.Equals(Path.GetExtension(args.Path), ".cue", StringComparison.OrdinalIgnoreCase))
+                {
+                    // if audio file exists of same name, return null
+                    return null;
+                }
+
+                var isMixedCollectionType = string.IsNullOrWhiteSpace(collectionType);
+
+                // For conflicting extensions, give priority to videos
+                if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path, libraryOptions))
+                {
+                    return null;
+                }
+
+                MediaBrowser.Controller.Entities.Audio.Audio item = null;
+
+                var isMusicCollectionType = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
+
+                // Use regular audio type for mixed libraries, owned items and music
+                if (isMixedCollectionType ||
+                    args.Parent == null ||
+                    isMusicCollectionType)
+                {
+                    item = new MediaBrowser.Controller.Entities.Audio.Audio();
+                }
+
+                else if (isBooksCollectionType)
+                {
+                    item = new AudioBook();
+                }
+
+                if (item != null)
+                {
+                    item.IsInMixedFolder = true;
+                }
+
+                return item;
+            }
+
+            return null;
+        }
 
 
-                        return null;
-                    }
+        private T FindAudio<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
+            where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
+        {
+            var multiDiscFolders = new List<FileSystemMetadata>();
 
 
-                    var collectionType = args.GetCollectionType();
+            var libraryOptions = args.GetLibraryOptions();
+            var filesFromOtherItems = new List<FileSystemMetadata>();
 
 
-                    var isMixed = string.IsNullOrWhiteSpace(collectionType);
+            // TODO: Allow GetMultiDiscMovie in here
+            var supportsMultiVersion = false;
 
 
-                    // For conflicting extensions, give priority to videos
-                    if (isMixed && _libraryManager.IsVideoFile(args.Path, libraryOptions))
-                    {
-                        return null;
-                    }
+            var result = ResolveMultipleAudio<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
+                new MultiItemResolverResult();
 
 
-                    var isStandalone = args.Parent == null;
+            if (result.Items.Count == 1)
+            {
+                var videoPath = result.Items[0].Path;
 
 
-                    if (isStandalone ||
-                        string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) ||
-                        isMixed)
-                    {
-                        return new MediaBrowser.Controller.Entities.Audio.Audio();
-                    }
+                // If we were supporting this we'd be checking filesFromOtherItems
+                var hasOtherItems = false;
 
 
-                    if (string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
-                    {
-                        return new AudioBook();
-                    }
+                if (!hasOtherItems)
+                {
+                    var item = (T)result.Items[0];
+                    item.IsInMixedFolder = false;
+                    item.Name = Path.GetFileName(item.ContainingFolderPath);
+                    return item;
                 }
                 }
             }
             }
 
 
+            if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
+            {
+                //return GetMultiDiscAudio<T>(multiDiscFolders, directoryService);
+            }
+
             return null;
             return null;
         }
         }
+
+        private MultiItemResolverResult ResolveMultipleAudio<T>(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, bool suppportMultiEditions, string collectionType, bool parseName)
+            where T : MediaBrowser.Controller.Entities.Audio.Audio, new()
+        {
+            var files = new List<FileSystemMetadata>();
+            var items = new List<BaseItem>();
+            var leftOver = new List<FileSystemMetadata>();
+
+            // Loop through each child file/folder and see if we find a video
+            foreach (var child in fileSystemEntries)
+            {
+                if (child.IsDirectory)
+                {
+                    leftOver.Add(child);
+                }
+                else if (IsIgnored(child.Name))
+                {
+
+                }
+                else
+                {
+                    files.Add(child);
+                }
+            }
+
+            var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
+
+            var resolver = new AudioBookListResolver(namingOptions);
+            var resolverResult = resolver.Resolve(files).ToList();
+
+            var result = new MultiItemResolverResult
+            {
+                ExtraFiles = leftOver,
+                Items = items
+            };
+
+            var isInMixedFolder = resolverResult.Count > 1 || (parent != null && parent.IsTopParent);
+
+            foreach (var resolvedItem in resolverResult)
+            {
+                if (resolvedItem.Files.Count > 1)
+                {
+                    // For now, until we sort out naming for multi-part books
+                    continue;
+                }
+
+                var firstMedia = resolvedItem.Files.First();
+                
+                var libraryItem = new T
+                {
+                    Path = firstMedia.Path,
+                    IsInMixedFolder = isInMixedFolder,
+                    ProductionYear = resolvedItem.Year,
+                    Name = parseName ?
+                        resolvedItem.Name :
+                        Path.GetFileNameWithoutExtension(firstMedia.Path),
+                    //AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(),
+                    //LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray()
+                };
+
+                result.Items.Add(libraryItem);
+            }
+
+            result.ExtraFiles.AddRange(files.Where(i => !ContainsFile(resolverResult, i)));
+
+            return result;
+        }
+
+        private bool ContainsFile(List<AudioBookInfo> result, FileSystemMetadata file)
+        {
+            return result.Any(i => ContainsFile(i, file));
+        }
+
+        private bool ContainsFile(AudioBookInfo result, FileSystemMetadata file)
+        {
+            return result.Files.Any(i => ContainsFile(i, file)) ||
+                result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
+                result.Extras.Any(i => ContainsFile(i, file));
+        }
+
+        private bool ContainsFile(AudioBookFileInfo result, FileSystemMetadata file)
+        {
+            return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase);
+        }
+
+        private bool IsIgnored(string filename)
+        {
+            return false;
+        }
     }
     }
 }
 }

+ 1 - 1
Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs

@@ -274,7 +274,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetFilePaths(fullPath).Any(i => string.Equals(Path.GetExtension(i), ".vob", StringComparison.OrdinalIgnoreCase));
+            return directoryService.GetFilePaths(fullPath).Any(i => string.Equals(Path.GetExtension(i), ".vob", StringComparison.OrdinalIgnoreCase));
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 21 - 16
Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -202,14 +202,14 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
         {
         {
             var collectionType = args.GetCollectionType();
             var collectionType = args.GetCollectionType();
 
 
-            if (IsInvalid(args.Parent, collectionType))
-            {
-                return null;
-            }
-
             // Find movies with their own folders
             // Find movies with their own folders
             if (args.IsDirectory)
             if (args.IsDirectory)
             {
             {
+                if (IsInvalid(args.Parent, collectionType))
+                {
+                    return null;
+                }
+
                 var files = args.FileSystemChildren
                 var files = args.FileSystemChildren
                     .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
                     .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
                     .ToList();
                     .ToList();
@@ -251,8 +251,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
                 return null;
                 return null;
             }
             }
 
 
-            // Owned items will be caught by the plain video resolver
+            // Handle owned items
             if (args.Parent == null)
             if (args.Parent == null)
+            {
+                return base.Resolve(args);
+            }
+
+            if (IsInvalid(args.Parent, collectionType))
             {
             {
                 return null;
                 return null;
             }
             }
@@ -528,6 +533,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
             return returnVideo;
             return returnVideo;
         }
         }
 
 
+        private string[] ValidCollectionTypes = new[]
+        {
+                CollectionType.Movies,
+                CollectionType.HomeVideos,
+                CollectionType.MusicVideos,
+                CollectionType.Movies,
+                CollectionType.Photos
+            };
+
         private bool IsInvalid(Folder parent, string collectionType)
         private bool IsInvalid(Folder parent, string collectionType)
         {
         {
             if (parent != null)
             if (parent != null)
@@ -538,21 +552,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
                 }
                 }
             }
             }
 
 
-            var validCollectionTypes = new[]
-            {
-                CollectionType.Movies,
-                CollectionType.HomeVideos,
-                CollectionType.MusicVideos,
-                CollectionType.Movies,
-                CollectionType.Photos
-            };
-
             if (string.IsNullOrWhiteSpace(collectionType))
             if (string.IsNullOrWhiteSpace(collectionType))
             {
             {
                 return false;
                 return false;
             }
             }
 
 
-            return !validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
+            return !ValidCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
         }
         }
 
 
         private IImageProcessor _imageProcessor;
         private IImageProcessor _imageProcessor;

+ 0 - 30
Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs

@@ -5,36 +5,6 @@ using MediaBrowser.Model.IO;
 
 
 namespace Emby.Server.Implementations.Library.Resolvers
 namespace Emby.Server.Implementations.Library.Resolvers
 {
 {
-    /// <summary>
-    /// Resolves a Path into a Video
-    /// </summary>
-    public class VideoResolver : BaseVideoResolver<Video>
-    {
-        protected override Video Resolve(ItemResolveArgs args)
-        {
-            if (args.Parent != null)
-            {
-                // The movie resolver will handle this
-                return null;
-            }
-
-            return base.Resolve(args);
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override ResolverPriority Priority
-        {
-            get { return ResolverPriority.Last; }
-        }
-
-        public VideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
-        {
-        }
-    }
-
     public class GenericVideoResolver<T> : BaseVideoResolver<T>
     public class GenericVideoResolver<T> : BaseVideoResolver<T>
         where T : Video, new ()
         where T : Video, new ()
     {
     {

+ 24 - 4
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -1052,10 +1052,27 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             {
             {
                 _liveStreamsSemaphore.Release();
                 _liveStreamsSemaphore.Release();
             }
             }
+        }
+
+        public async Task<List<ILiveStream>> GetLiveStreams(TunerHostInfo host, CancellationToken cancellationToken)
+        {
+            //await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
 
 
+            //try
+            //{
+            var hostId = host.Id;
+
+            return _liveStreams
+                .Where(i => string.Equals(i.TunerHostId, hostId, StringComparison.OrdinalIgnoreCase))
+                .ToList();
+            //}
+            //finally
+            //{
+            //    _liveStreamsSemaphore.Release();
+            //}
         }
         }
 
 
-        private async Task<Tuple<ILiveStream, MediaSourceInfo, ITunerHost>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
+        private async Task<Tuple<ILiveStream, MediaSourceInfo>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
         {
         {
             _logger.Info("Streaming Channel " + channelId);
             _logger.Info("Streaming Channel " + channelId);
 
 
@@ -1072,7 +1089,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
                     _logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount);
                     _logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount);
 
 
-                    return new Tuple<ILiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, result.TunerHost);
+                    return new Tuple<ILiveStream, MediaSourceInfo>(result, openedMediaSource);
                 }
                 }
 
 
                 foreach (var hostInstance in _liveTvManager.TunerHosts)
                 foreach (var hostInstance in _liveTvManager.TunerHosts)
@@ -1086,13 +1103,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                         result.SharedStreamIds.Add(openedMediaSource.Id);
                         result.SharedStreamIds.Add(openedMediaSource.Id);
                         _liveStreams.Add(result);
                         _liveStreams.Add(result);
 
 
-                        result.TunerHost = hostInstance;
                         result.OriginalStreamId = streamId;
                         result.OriginalStreamId = streamId;
 
 
                         _logger.Info("Returning mediasource streamId {0}, mediaSource.Id {1}, mediaSource.LiveStreamId {2}",
                         _logger.Info("Returning mediasource streamId {0}, mediaSource.Id {1}, mediaSource.LiveStreamId {2}",
                             streamId, openedMediaSource.Id, openedMediaSource.LiveStreamId);
                             streamId, openedMediaSource.Id, openedMediaSource.LiveStreamId);
 
 
-                        return new Tuple<ILiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, hostInstance);
+                        return new Tuple<ILiveStream, MediaSourceInfo>(result, openedMediaSource);
                     }
                     }
                     catch (FileNotFoundException)
                     catch (FileNotFoundException)
                     {
                     {
@@ -2445,6 +2461,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                             {
                             {
                                 existingTimer.Status = RecordingStatus.Cancelled;
                                 existingTimer.Status = RecordingStatus.Cancelled;
                             }
                             }
+                            else if (!existingTimer.IsManual)
+                            {
+                                existingTimer.Status = RecordingStatus.New;
+                            }
 
 
                             if (existingTimer.Status != RecordingStatus.Cancelled)
                             if (existingTimer.Status != RecordingStatus.Cancelled)
                             {
                             {

+ 11 - 11
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -1232,6 +1232,8 @@ namespace Emby.Server.Implementations.LiveTv
             var newChannelIdList = new List<Guid>();
             var newChannelIdList = new List<Guid>();
             var newProgramIdList = new List<Guid>();
             var newProgramIdList = new List<Guid>();
 
 
+            var cleanDatabase = true;
+
             foreach (var service in _services)
             foreach (var service in _services)
             {
             {
                 cancellationToken.ThrowIfCancellationRequested();
                 cancellationToken.ThrowIfCancellationRequested();
@@ -1254,6 +1256,7 @@ namespace Emby.Server.Implementations.LiveTv
                 }
                 }
                 catch (Exception ex)
                 catch (Exception ex)
                 {
                 {
+                    cleanDatabase = false;
                     _logger.ErrorException("Error refreshing channels for service", ex);
                     _logger.ErrorException("Error refreshing channels for service", ex);
                 }
                 }
 
 
@@ -1264,8 +1267,11 @@ namespace Emby.Server.Implementations.LiveTv
                 progress.Report(100 * percent);
                 progress.Report(100 * percent);
             }
             }
 
 
-            await CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
-            await CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
+            if (cleanDatabase)
+            {
+                await CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
+                await CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
+            }
 
 
             var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
             var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
 
 
@@ -1291,8 +1297,9 @@ namespace Emby.Server.Implementations.LiveTv
         {
         {
             progress.Report(10);
             progress.Report(10);
 
 
-            var allChannels = await GetChannels(service, cancellationToken).ConfigureAwait(false);
-            var allChannelsList = allChannels.ToList();
+            var allChannelsList = (await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false))
+                .Select(i => new Tuple<string, ChannelInfo>(service.Name, i))
+                .ToList();
 
 
             var list = new List<LiveTvChannel>();
             var list = new List<LiveTvChannel>();
 
 
@@ -1507,13 +1514,6 @@ namespace Emby.Server.Implementations.LiveTv
             return 7;
             return 7;
         }
         }
 
 
-        private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
-        {
-            var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
-
-            return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
-        }
-
         private DateTime _lastRecordingRefreshTime;
         private DateTime _lastRecordingRefreshTime;
         private async Task RefreshRecordings(Guid internalLiveTvFolderId, CancellationToken cancellationToken)
         private async Task RefreshRecordings(Guid internalLiveTvFolderId, CancellationToken cancellationToken)
         {
         {

+ 93 - 21
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs

@@ -105,7 +105,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
         private class HdHomerunChannelInfo : ChannelInfo
         private class HdHomerunChannelInfo : ChannelInfo
         {
         {
             public bool IsLegacyTuner { get; set; }
             public bool IsLegacyTuner { get; set; }
-            public string Url { get; set; }
         }
         }
 
 
         protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
         protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
@@ -124,7 +123,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 VideoCodec = i.VideoCodec,
                 VideoCodec = i.VideoCodec,
                 ChannelType = ChannelType.TV,
                 ChannelType = ChannelType.TV,
                 IsLegacyTuner = (i.URL ?? string.Empty).StartsWith("hdhomerun", StringComparison.OrdinalIgnoreCase),
                 IsLegacyTuner = (i.URL ?? string.Empty).StartsWith("hdhomerun", StringComparison.OrdinalIgnoreCase),
-                Url = i.URL
+                Path = i.URL
 
 
             }).Cast<ChannelInfo>().ToList();
             }).Cast<ChannelInfo>().ToList();
         }
         }
@@ -148,7 +147,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             {
             {
                 using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
                 using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
                 {
                 {
-                    Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
+                    Url = string.Format("{0}/discover.json", GetApiUrl(info)),
                     CancellationToken = cancellationToken,
                     CancellationToken = cancellationToken,
                     TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
                     TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
                     BufferContent = false
                     BufferContent = false
@@ -195,13 +194,80 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             }
             }
         }
         }
 
 
-        private async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
+        private async Task<List<LiveTvTunerInfo>> GetTunerInfosHttp(TunerHostInfo info, CancellationToken cancellationToken)
+        {
+            var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+
+            using (var stream = await _httpClient.Get(new HttpRequestOptions()
+            {
+                Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
+                CancellationToken = cancellationToken,
+                TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
+                BufferContent = false
+            }))
+            {
+                var tuners = new List<LiveTvTunerInfo>();
+                using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
+                {
+                    while (!sr.EndOfStream)
+                    {
+                        string line = StripXML(sr.ReadLine());
+                        if (line.Contains("Channel"))
+                        {
+                            LiveTvTunerStatus status;
+                            var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
+                            var name = line.Substring(0, index - 1);
+                            var currentChannel = line.Substring(index + 7);
+                            if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; }
+                            tuners.Add(new LiveTvTunerInfo
+                            {
+                                Name = name,
+                                SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
+                                ProgramName = currentChannel,
+                                Status = status
+                            });
+                        }
+                    }
+                }
+                return tuners;
+            }
+        }
+
+        private static string StripXML(string source)
+        {
+            char[] buffer = new char[source.Length];
+            int bufferIndex = 0;
+            bool inside = false;
+
+            for (int i = 0; i < source.Length; i++)
+            {
+                char let = source[i];
+                if (let == '<')
+                {
+                    inside = true;
+                    continue;
+                }
+                if (let == '>')
+                {
+                    inside = false;
+                    continue;
+                }
+                if (!inside)
+                {
+                    buffer[bufferIndex] = let;
+                    bufferIndex++;
+                }
+            }
+            return new string(buffer, 0, bufferIndex);
+        }
+
+        private async Task<List<LiveTvTunerInfo>> GetTunerInfosUdp(TunerHostInfo info, CancellationToken cancellationToken)
         {
         {
             var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
             var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
 
 
             var tuners = new List<LiveTvTunerInfo>();
             var tuners = new List<LiveTvTunerInfo>();
 
 
-            var uri = new Uri(GetApiUrl(info, false));
+            var uri = new Uri(GetApiUrl(info));
 
 
             using (var manager = new HdHomerunManager(_socketFactory, Logger))
             using (var manager = new HdHomerunManager(_socketFactory, Logger))
             {
             {
@@ -246,7 +312,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             return list;
             return list;
         }
         }
 
 
-        private string GetApiUrl(TunerHostInfo info, bool isPlayback)
+        public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
+        {
+            // TODO Need faster way to determine UDP vs HTTP
+            var channels = await GetChannels(info, true, cancellationToken);
+
+            var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo;
+
+            if (hdHomerunChannelInfo == null || hdHomerunChannelInfo.IsLegacyTuner)
+            {
+                return await GetTunerInfosUdp(info, cancellationToken).ConfigureAwait(false);
+            }
+
+            return await GetTunerInfosHttp(info, cancellationToken).ConfigureAwait(false);
+        }
+
+        private string GetApiUrl(TunerHostInfo info)
         {
         {
             var url = info.Url;
             var url = info.Url;
 
 
@@ -260,16 +341,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 url = "http://" + url;
                 url = "http://" + url;
             }
             }
 
 
-            var uri = new Uri(url);
-
-            if (isPlayback)
-            {
-                var builder = new UriBuilder(uri);
-                builder.Port = 5004;
-                uri = builder.Uri;
-            }
-
-            return uri.AbsoluteUri.TrimEnd('/');
+            return new Uri(url).AbsoluteUri.TrimEnd('/');
         }
         }
 
 
         private class Channels
         private class Channels
@@ -392,7 +464,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 nal = "0";
                 nal = "0";
             }
             }
 
 
-            var url = GetApiUrl(info, false);
+            var url = GetApiUrl(info);
 
 
             var id = profile;
             var id = profile;
             if (string.IsNullOrWhiteSpace(id))
             if (string.IsNullOrWhiteSpace(id))
@@ -526,7 +598,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
             if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
             if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
             {
             {
-                return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
+                return new HdHomerunUdpStream(mediaSource, info, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
             }
             }
 
 
             // The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet
             // The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet
@@ -537,7 +609,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             {
             {
                 mediaSource.Protocol = MediaProtocol.Http;
                 mediaSource.Protocol = MediaProtocol.Http;
 
 
-                var httpUrl = GetApiUrl(info, true) + "/auto/v" + hdhrId;
+                var httpUrl = channelInfo.Path;
 
 
                 // If raw was used, the tuner doesn't support params
                 // If raw was used, the tuner doesn't support params
                 if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
                 if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
@@ -546,10 +618,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 }
                 }
                 mediaSource.Path = httpUrl;
                 mediaSource.Path = httpUrl;
 
 
-                return new HdHomerunHttpStream(mediaSource, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment);
+                return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment);
             }
             }
 
 
-            return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
+            return new HdHomerunUdpStream(mediaSource, info, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
         }
         }
 
 
         public async Task Validate(TunerHostInfo info)
         public async Task Validate(TunerHostInfo info)

+ 7 - 4
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs

@@ -11,6 +11,7 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
+using MediaBrowser.Model.LiveTv;
 
 
 namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 {
 {
@@ -23,8 +24,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
         private readonly int _numTuners;
         private readonly int _numTuners;
         private readonly INetworkManager _networkManager;
         private readonly INetworkManager _networkManager;
 
 
-        public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment)
-            : base(mediaSource, environment, fileSystem, logger, appPaths)
+        public HdHomerunUdpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment)
+            : base(mediaSource, tunerHostInfo, environment, fileSystem, logger, appPaths)
         {
         {
             _appHost = appHost;
             _appHost = appHost;
             _socketFactory = socketFactory;
             _socketFactory = socketFactory;
@@ -32,6 +33,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             OriginalStreamId = originalStreamId;
             OriginalStreamId = originalStreamId;
             _channelCommands = channelCommands;
             _channelCommands = channelCommands;
             _numTuners = numTuners;
             _numTuners = numTuners;
+            EnableStreamSharing = true;
         }
         }
 
 
         public override async Task Open(CancellationToken openCancellationToken)
         public override async Task Open(CancellationToken openCancellationToken)
@@ -105,9 +107,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             await taskCompletionSource.Task.ConfigureAwait(false);
             await taskCompletionSource.Task.ConfigureAwait(false);
         }
         }
 
 
-        public override void Close()
+        protected override void CloseInternal()
         {
         {
-            Logger.Info("Closing HDHR UDP live stream");
             LiveStreamCancellationTokenSource.Cancel();
             LiveStreamCancellationTokenSource.Cancel();
         }
         }
 
 
@@ -134,6 +135,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                             openTaskCompletionSource.TrySetException(ex);
                             openTaskCompletionSource.TrySetException(ex);
                         }
                         }
 
 
+                        EnableStreamSharing = false;
+
                         try
                         try
                         {
                         {
                             await hdHomerunManager.StopStreaming().ConfigureAwait(false);
                             await hdHomerunManager.StopStreaming().ConfigureAwait(false);

+ 27 - 5
Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs

@@ -10,6 +10,7 @@ using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
+using MediaBrowser.Model.LiveTv;
 
 
 namespace Emby.Server.Implementations.LiveTv.TunerHosts
 namespace Emby.Server.Implementations.LiveTv.TunerHosts
 {
 {
@@ -21,7 +22,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         {
         {
             get { return SharedStreamIds.Count; }
             get { return SharedStreamIds.Count; }
         }
         }
-        public ITunerHost TunerHost { get; set; }
+
         public string OriginalStreamId { get; set; }
         public string OriginalStreamId { get; set; }
         public bool EnableStreamSharing { get; set; }
         public bool EnableStreamSharing { get; set; }
         public string UniqueId { get; private set; }
         public string UniqueId { get; private set; }
@@ -29,12 +30,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         public List<string> SharedStreamIds { get; private set; }
         public List<string> SharedStreamIds { get; private set; }
         protected readonly IEnvironmentInfo Environment;
         protected readonly IEnvironmentInfo Environment;
         protected readonly IFileSystem FileSystem;
         protected readonly IFileSystem FileSystem;
+        protected readonly IServerApplicationPaths AppPaths;
 
 
-        protected readonly string TempFilePath;
+        protected  string TempFilePath;
         protected readonly ILogger Logger;
         protected readonly ILogger Logger;
         protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
         protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
 
 
-        public LiveStream(MediaSourceInfo mediaSource, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
+        public string TunerHostId { get; private set; }
+
+        public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
         {
         {
             OriginalMediaSource = mediaSource;
             OriginalMediaSource = mediaSource;
             Environment = environment;
             Environment = environment;
@@ -44,7 +48,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             EnableStreamSharing = true;
             EnableStreamSharing = true;
             SharedStreamIds = new List<string>();
             SharedStreamIds = new List<string>();
             UniqueId = Guid.NewGuid().ToString("N");
             UniqueId = Guid.NewGuid().ToString("N");
-            TempFilePath = Path.Combine(appPaths.GetTranscodingTempPath(), UniqueId + ".ts");
+            TunerHostId = tuner.Id;
+
+            AppPaths = appPaths;
+
+            SetTempFilePath("ts");
+        }
+
+        protected void SetTempFilePath(string extension)
+        {
+            TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
         }
         }
 
 
         public virtual Task Open(CancellationToken openCancellationToken)
         public virtual Task Open(CancellationToken openCancellationToken)
@@ -52,7 +65,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
             return Task.FromResult(true);
             return Task.FromResult(true);
         }
         }
 
 
-        public virtual void Close()
+        public void Close()
+        {
+            EnableStreamSharing = false;
+
+            Logger.Info("Closing " + GetType().Name);
+
+            CloseInternal();
+        }
+
+        protected virtual void CloseInternal()
         {
         {
         }
         }
 
 

+ 20 - 2
Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -77,10 +77,28 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
 
         protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
         protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
         {
         {
+            var tunerCount = info.TunerCount;
+
+            if (tunerCount > 0)
+            {
+                var liveStreams = await EmbyTV.EmbyTV.Current.GetLiveStreams(info, cancellationToken).ConfigureAwait(false);
+
+                if (liveStreams.Count >= info.TunerCount)
+                {
+                    throw new LiveTvConflictException();
+                }
+            }
+
             var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false);
             var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false);
 
 
-            var liveStream = new LiveStream(sources.First(), _environment, FileSystem, Logger, Config.ApplicationPaths);
-            return liveStream;
+            var mediaSource = sources.First();
+
+            if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping)
+            {
+                return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment);
+            }
+
+            return new LiveStream(mediaSource, info, _environment, FileSystem, Logger, Config.ApplicationPaths);
         }
         }
 
 
         public async Task Validate(TunerHostInfo info)
         public async Task Validate(TunerHostInfo info)

+ 54 - 19
Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHttpStream.cs → Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs

@@ -14,20 +14,22 @@ using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
 using System.Globalization;
 using System.Globalization;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
+using MediaBrowser.Model.LiveTv;
 
 
-namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
+namespace Emby.Server.Implementations.LiveTv.TunerHosts
 {
 {
-    public class HdHomerunHttpStream : LiveStream, IDirectStreamProvider
+    public class SharedHttpStream : LiveStream, IDirectStreamProvider
     {
     {
         private readonly IHttpClient _httpClient;
         private readonly IHttpClient _httpClient;
         private readonly IServerApplicationHost _appHost;
         private readonly IServerApplicationHost _appHost;
 
 
-        public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment)
-            : base(mediaSource, environment, fileSystem, logger, appPaths)
+        public SharedHttpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment)
+            : base(mediaSource, tunerHostInfo, environment, fileSystem, logger, appPaths)
         {
         {
             _httpClient = httpClient;
             _httpClient = httpClient;
             _appHost = appHost;
             _appHost = appHost;
             OriginalStreamId = originalStreamId;
             OriginalStreamId = originalStreamId;
+            EnableStreamSharing = true;
         }
         }
 
 
         public override async Task Open(CancellationToken openCancellationToken)
         public override async Task Open(CancellationToken openCancellationToken)
@@ -40,7 +42,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
 
             FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath));
             FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath));
 
 
-            Logger.Info("Opening HDHR Live stream from {0}", url);
+            var typeName = GetType().Name;
+            Logger.Info("Opening " + typeName + " Live stream from {0}", url);
 
 
             var response = await _httpClient.SendAsync(new HttpRequestOptions
             var response = await _httpClient.SendAsync(new HttpRequestOptions
             {
             {
@@ -51,13 +54,41 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 // Increase a little bit
                 // Increase a little bit
                 TimeoutMs = 30000,
                 TimeoutMs = 30000,
 
 
-                EnableHttpCompression = false
+                EnableHttpCompression = false,
+
+                LogResponse = true,
+                LogResponseHeaders = true
 
 
             }, "GET").ConfigureAwait(false);
             }, "GET").ConfigureAwait(false);
 
 
-            Logger.Info("Opened HDHR stream from {0}", url);
+            var extension = "ts";
+            var requiresRemux = false;
+
+            var contentType = response.ContentType ?? string.Empty;
+            if (contentType.IndexOf("matroska", StringComparison.OrdinalIgnoreCase) != -1)
+            {
+                requiresRemux = true;
+            }
+            else if (contentType.IndexOf("mp4", StringComparison.OrdinalIgnoreCase) != -1 ||
+               contentType.IndexOf("dash", StringComparison.OrdinalIgnoreCase) != -1 ||
+               contentType.IndexOf("mpegURL", StringComparison.OrdinalIgnoreCase) != -1)
+            {
+                requiresRemux = true;
+            }
+
+            // Close the stream without any sharing features
+            if (requiresRemux)
+            {
+                using (response)
+                {
+                    return;
+                }
+            }
+
+            SetTempFilePath(extension);
 
 
-            StartStreaming(response, LiveStreamCancellationTokenSource.Token);
+            var taskCompletionSource = new TaskCompletionSource<bool>();
+            StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
 
 
             //OpenedMediaSource.Protocol = MediaProtocol.File;
             //OpenedMediaSource.Protocol = MediaProtocol.File;
             //OpenedMediaSource.Path = tempFile;
             //OpenedMediaSource.Path = tempFile;
@@ -66,20 +97,28 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
             OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
             OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
             OpenedMediaSource.Protocol = MediaProtocol.Http;
             OpenedMediaSource.Protocol = MediaProtocol.Http;
 
 
+            if (OpenedMediaSource.SupportsProbing)
+            {
+                await Task.Delay(3000).ConfigureAwait(false);
+            }
+
+            //OpenedMediaSource.Path = TempFilePath;
+            //OpenedMediaSource.Protocol = MediaProtocol.File;
+
             //OpenedMediaSource.Path = _tempFilePath;
             //OpenedMediaSource.Path = _tempFilePath;
             //OpenedMediaSource.Protocol = MediaProtocol.File;
             //OpenedMediaSource.Protocol = MediaProtocol.File;
             //OpenedMediaSource.SupportsDirectPlay = false;
             //OpenedMediaSource.SupportsDirectPlay = false;
             //OpenedMediaSource.SupportsDirectStream = true;
             //OpenedMediaSource.SupportsDirectStream = true;
             //OpenedMediaSource.SupportsTranscoding = true;
             //OpenedMediaSource.SupportsTranscoding = true;
+            await taskCompletionSource.Task.ConfigureAwait(false);
         }
         }
 
 
-        public override void Close()
+        protected override void CloseInternal()
         {
         {
-            Logger.Info("Closing HDHR live stream");
             LiveStreamCancellationTokenSource.Cancel();
             LiveStreamCancellationTokenSource.Cancel();
         }
         }
 
 
-        private Task StartStreaming(HttpResponseInfo response, CancellationToken cancellationToken)
+        private Task StartStreaming(HttpResponseInfo response, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
         {
         {
             return Task.Run(async () =>
             return Task.Run(async () =>
             {
             {
@@ -89,12 +128,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                     {
                     {
                         using (var stream = response.Content)
                         using (var stream = response.Content)
                         {
                         {
-                            Logger.Info("Beginning HdHomerunHttpStream stream to file");
+                            Logger.Info("Beginning {0} stream to {1}", GetType().Name, TempFilePath);
 
 
-                            FileSystem.CreateDirectory(FileSystem.GetDirectoryName(TempFilePath));
                             using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
                             using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
                             {
                             {
-                                StreamHelper.CopyTo(stream, fileStream, 81920, null, cancellationToken);
+                                StreamHelper.CopyTo(stream, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken);
                             }
                             }
                         }
                         }
                     }
                     }
@@ -106,17 +144,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 {
                 {
                     Logger.ErrorException("Error copying live stream.", ex);
                     Logger.ErrorException("Error copying live stream.", ex);
                 }
                 }
-
+                EnableStreamSharing = false;
                 await DeleteTempFile(TempFilePath).ConfigureAwait(false);
                 await DeleteTempFile(TempFilePath).ConfigureAwait(false);
             });
             });
         }
         }
 
 
         private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
         private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
         {
         {
-            Task.Run(() =>
-            {
-                openTaskCompletionSource.TrySetResult(true);
-            });
+            openTaskCompletionSource.TrySetResult(true);
         }
         }
     }
     }
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/ar.json

@@ -87,10 +87,5 @@
     "User": "\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645",
     "User": "\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645",
     "System": "\u0627\u0644\u0646\u0638\u0627\u0645",
     "System": "\u0627\u0644\u0646\u0638\u0627\u0645",
     "Application": "\u0627\u0644\u062a\u0637\u0628\u064a\u0642",
     "Application": "\u0627\u0644\u062a\u0637\u0628\u064a\u0642",
-    "Plugin": "\u0627\u0644\u0645\u0644\u062d\u0642",
-    "LabelExit": "\u062e\u0631\u0648\u062c",
-    "LabelVisitCommunity": "\u0632\u064a\u0627\u0631\u0629 \u0627\u0644\u0645\u062c\u062a\u0645\u0639",
-    "LabelBrowseLibrary": "\u062a\u0635\u0641\u062d \u0627\u0644\u0645\u0643\u062a\u0628\u0629",
-    "LabelConfigureServer": "\u0636\u0628\u0637 \u0625\u0639\u062f\u0627\u062f\u0627\u062a \u0623\u0645\u0628\u064a",
-    "LabelRestartServer": "\u0625\u0639\u0627\u062f\u0629 \u062a\u0634\u063a\u064a\u0644 \u0627\u0644\u062e\u0627\u062f\u0645"
+    "Plugin": "\u0627\u0644\u0645\u0644\u062d\u0642"
 }
 }

+ 43 - 48
Emby.Server.Implementations/Localization/Core/bg-BG.json

@@ -1,12 +1,12 @@
 {
 {
     "Latest": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438",
     "Latest": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438",
     "ValueSpecialEpisodeName": "\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u043d\u0438 - {0}",
     "ValueSpecialEpisodeName": "\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u043d\u0438 - {0}",
-    "Inherit": "Inherit",
+    "Inherit": "\u041d\u0430\u0441\u043b\u0435\u0434\u044f\u0432\u0430\u043d\u0435",
     "Books": "\u041a\u043d\u0438\u0433\u0438",
     "Books": "\u041a\u043d\u0438\u0433\u0438",
     "Music": "\u041c\u0443\u0437\u0438\u043a\u0430",
     "Music": "\u041c\u0443\u0437\u0438\u043a\u0430",
     "Games": "\u0418\u0433\u0440\u0438",
     "Games": "\u0418\u0433\u0440\u0438",
     "Photos": "\u0421\u043d\u0438\u043c\u043a\u0438",
     "Photos": "\u0421\u043d\u0438\u043c\u043a\u0438",
-    "MixedContent": "Mixed content",
+    "MixedContent": "\u0421\u043c\u0435\u0441\u0435\u043d\u043e \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435",
     "MusicVideos": "\u041c\u0443\u0437\u0438\u043a\u0430\u043b\u043d\u0438 \u043a\u043b\u0438\u043f\u043e\u0432\u0435",
     "MusicVideos": "\u041c\u0443\u0437\u0438\u043a\u0430\u043b\u043d\u0438 \u043a\u043b\u0438\u043f\u043e\u0432\u0435",
     "HomeVideos": "\u0414\u043e\u043c\u0430\u0448\u043d\u0438 \u043a\u043b\u0438\u043f\u043e\u0432\u0435",
     "HomeVideos": "\u0414\u043e\u043c\u0430\u0448\u043d\u0438 \u043a\u043b\u0438\u043f\u043e\u0432\u0435",
     "Playlists": "\u0421\u043f\u0438\u0441\u044a\u0446\u0438",
     "Playlists": "\u0421\u043f\u0438\u0441\u044a\u0446\u0438",
@@ -17,8 +17,8 @@
     "HeaderAlbumArtists": "\u0418\u0437\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b\u0438 \u043d\u0430 \u0430\u043b\u0431\u0443\u043c\u0438",
     "HeaderAlbumArtists": "\u0418\u0437\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b\u0438 \u043d\u0430 \u0430\u043b\u0431\u0443\u043c\u0438",
     "HeaderFavoriteAlbums": "\u041b\u044e\u0431\u0438\u043c\u0438 \u0430\u043b\u0431\u0443\u043c\u0438",
     "HeaderFavoriteAlbums": "\u041b\u044e\u0431\u0438\u043c\u0438 \u0430\u043b\u0431\u0443\u043c\u0438",
     "HeaderFavoriteEpisodes": "\u041b\u044e\u0431\u0438\u043c\u0438 \u0435\u043f\u0438\u0437\u043e\u0434\u0438",
     "HeaderFavoriteEpisodes": "\u041b\u044e\u0431\u0438\u043c\u0438 \u0435\u043f\u0438\u0437\u043e\u0434\u0438",
-    "HeaderFavoriteShows": "Favorite Shows",
-    "HeaderNextUp": "Next Up",
+    "HeaderFavoriteShows": "\u041b\u044e\u0431\u0438\u043c\u0438 \u0441\u0435\u0440\u0438\u0430\u043b\u0438",
+    "HeaderNextUp": "\u0421\u043b\u0435\u0434\u0432\u0430",
     "Favorites": "\u041b\u044e\u0431\u0438\u043c\u0438",
     "Favorites": "\u041b\u044e\u0431\u0438\u043c\u0438",
     "Collections": "\u041a\u043e\u043b\u0435\u043a\u0446\u0438\u0438",
     "Collections": "\u041a\u043e\u043b\u0435\u043a\u0446\u0438\u0438",
     "Channels": "\u041a\u0430\u043d\u0430\u043b\u0438",
     "Channels": "\u041a\u0430\u043d\u0430\u043b\u0438",
@@ -27,70 +27,65 @@
     "Artists": "\u0418\u0437\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b\u0438",
     "Artists": "\u0418\u0437\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b\u0438",
     "Folders": "\u041f\u0430\u043f\u043a\u0438",
     "Folders": "\u041f\u0430\u043f\u043a\u0438",
     "Songs": "\u041f\u0435\u0441\u043d\u0438",
     "Songs": "\u041f\u0435\u0441\u043d\u0438",
-    "TvShows": "TV Shows",
+    "TvShows": "\u0422\u0435\u043b\u0435\u0432\u0438\u0437\u0438\u043e\u043d\u043d\u0438 \u0441\u0435\u0440\u0438\u0430\u043b\u0438",
     "Shows": "\u0421\u0435\u0440\u0438\u0430\u043b\u0438",
     "Shows": "\u0421\u0435\u0440\u0438\u0430\u043b\u0438",
     "Genres": "\u0416\u0430\u043d\u0440\u043e\u0432\u0435",
     "Genres": "\u0416\u0430\u043d\u0440\u043e\u0432\u0435",
     "NameSeasonNumber": "\u0421\u0435\u0437\u043e\u043d {0}",
     "NameSeasonNumber": "\u0421\u0435\u0437\u043e\u043d {0}",
-    "AppDeviceValues": "App: {0}, Device: {1}",
+    "AppDeviceValues": "\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u0430: {0}, \u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e: {1}",
     "UserDownloadingItemWithValues": "{0} is downloading {1}",
     "UserDownloadingItemWithValues": "{0} is downloading {1}",
-    "HeaderLiveTV": "Live TV",
-    "ChapterNameValue": "Chapter {0}",
-    "ScheduledTaskFailedWithName": "{0} failed",
+    "HeaderLiveTV": "\u0422\u0435\u043b\u0435\u0432\u0438\u0437\u0438\u044f \u043d\u0430 \u0436\u0438\u0432\u043e",
+    "ChapterNameValue": "\u0413\u043b\u0430\u0432\u0430 {0}",
+    "ScheduledTaskFailedWithName": "{0} \u0441\u0435 \u043f\u0440\u043e\u0432\u0430\u043b\u0438",
     "LabelRunningTimeValue": "Running time: {0}",
     "LabelRunningTimeValue": "Running time: {0}",
     "ScheduledTaskStartedWithName": "{0} \u0437\u0430\u043f\u043e\u0447\u043d\u0430",
     "ScheduledTaskStartedWithName": "{0} \u0437\u0430\u043f\u043e\u0447\u043d\u0430",
-    "VersionNumber": "Version {0}",
-    "PluginInstalledWithName": "{0} was installed",
-    "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly.",
-    "PluginUpdatedWithName": "{0} was updated",
+    "VersionNumber": "\u0412\u0435\u0440\u0441\u0438\u044f {0}",
+    "PluginInstalledWithName": "{0} \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e",
+    "StartupEmbyServerIsLoading": "\u0421\u044a\u0440\u0432\u044a\u0440\u044a\u0442 \u0437\u0430\u0440\u0435\u0436\u0434\u0430. \u041c\u043e\u043b\u044f, \u043e\u043f\u0438\u0442\u0430\u0439\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e \u0441\u043b\u0435\u0434 \u043c\u0430\u043b\u043a\u043e.",
+    "PluginUpdatedWithName": "{0} \u0435 \u043e\u0431\u043d\u043e\u0432\u0435\u043d\u043e",
     "PluginUninstalledWithName": "{0} \u0435 \u0434\u0435\u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e",
     "PluginUninstalledWithName": "{0} \u0435 \u0434\u0435\u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e",
-    "ItemAddedWithName": "{0} was added to the library",
-    "ItemRemovedWithName": "{0} was removed from the library",
-    "LabelIpAddressValue": "Ip address: {0}",
-    "DeviceOnlineWithName": "{0} is connected",
-    "UserOnlineFromDevice": "{0} is online from {1}",
-    "ProviderValue": "Provider: {0}",
-    "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
-    "UserCreatedWithName": "User {0} has been created",
-    "UserPasswordChangedWithName": "Password has been changed for user {0}",
-    "UserDeletedWithName": "User {0} has been deleted",
+    "ItemAddedWithName": "{0} \u0435 \u0434\u043e\u0431\u0430\u0432\u0435\u043d\u043e \u043a\u044a\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u0442\u0430",
+    "ItemRemovedWithName": "{0} \u0435 \u043f\u0440\u0435\u043c\u0430\u0445\u043d\u0430\u0442\u043e \u043e\u0442 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u0442\u0430",
+    "LabelIpAddressValue": "\u0418\u041f \u0430\u0434\u0440\u0435\u0441: {0}",
+    "DeviceOnlineWithName": "{0} \u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d",
+    "UserOnlineFromDevice": "{0} \u0435 \u043d\u0430 \u043b\u0438\u043d\u0438\u044f \u043e\u0442 {1}",
+    "ProviderValue": "\u0414\u043e\u0441\u0442\u0430\u0432\u0447\u0438\u043a: {0}",
+    "SubtitlesDownloadedForItem": "\u0418\u0437\u0442\u0435\u0433\u043b\u0435\u043d\u0438 \u0441\u0430 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u0438 \u0437\u0430 {0}",
+    "UserCreatedWithName": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f\u0442 {0} \u0435 \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u043d",
+    "UserPasswordChangedWithName": "\u041f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u043d\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f {0} \u0435 \u043f\u0440\u043e\u043c\u0435\u043d\u0435\u043d\u0430",
+    "UserDeletedWithName": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f\u0442 {0} \u0435 \u0438\u0437\u0442\u0440\u0438\u0442",
     "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
     "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
     "MessageServerConfigurationUpdated": "Server configuration has been updated",
     "MessageServerConfigurationUpdated": "Server configuration has been updated",
     "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
     "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
-    "MessageApplicationUpdated": "Emby Server has been updated",
+    "MessageApplicationUpdated": "\u0421\u044a\u0440\u0432\u044a\u0440\u044a\u0442 \u0435 \u043e\u0431\u043d\u043e\u0432\u0435\u043d",
     "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
     "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
-    "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
-    "UserOfflineFromDevice": "{0} has disconnected from {1}",
-    "DeviceOfflineWithName": "{0} has disconnected",
+    "AuthenticationSucceededWithUserName": "{0} \u0441\u0435 \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e",
+    "UserOfflineFromDevice": "{0} \u0441\u0435 \u0440\u0430\u0437\u043a\u0430\u0447\u0438 \u043e\u0442 {1}",
+    "DeviceOfflineWithName": "{0} \u0441\u0435 \u0440\u0430\u0437\u043a\u0430\u0447\u0438",
     "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
     "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
     "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
     "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
-    "NotificationOptionPluginError": "Plugin failure",
-    "NotificationOptionApplicationUpdateAvailable": "Application update available",
-    "NotificationOptionApplicationUpdateInstalled": "Application update installed",
+    "NotificationOptionPluginError": "\u0413\u0440\u0435\u0448\u043a\u0430 \u0432 \u043f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430",
+    "NotificationOptionApplicationUpdateAvailable": "\u041d\u0430\u043b\u0438\u0447\u043d\u043e \u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u0430 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0430\u0442\u0430",
+    "NotificationOptionApplicationUpdateInstalled": "\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u0442\u043e \u043d\u0430 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0430\u0442\u0430 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e",
     "NotificationOptionPluginUpdateInstalled": "\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u0442\u043e \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e",
     "NotificationOptionPluginUpdateInstalled": "\u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435\u0442\u043e \u043d\u0430 \u043f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u043e",
     "NotificationOptionPluginInstalled": "\u041f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0430",
     "NotificationOptionPluginInstalled": "\u041f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0430",
     "NotificationOptionPluginUninstalled": "\u041f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0434\u0435\u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0430",
     "NotificationOptionPluginUninstalled": "\u041f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430\u0442\u0430 \u0435 \u0434\u0435\u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0430",
-    "NotificationOptionVideoPlayback": "Video playback started",
-    "NotificationOptionAudioPlayback": "Audio playback started",
-    "NotificationOptionGamePlayback": "Game playback started",
-    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
-    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
-    "NotificationOptionGamePlaybackStopped": "Game playback stopped",
-    "NotificationOptionTaskFailed": "Scheduled task failure",
-    "NotificationOptionInstallationFailed": "Installation failure",
-    "NotificationOptionNewLibraryContent": "New content added",
-    "NotificationOptionCameraImageUploaded": "Camera image uploaded",
+    "NotificationOptionVideoPlayback": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0432\u0438\u0434\u0435\u043e \u0437\u0430\u043f\u043e\u0447\u043d\u0430",
+    "NotificationOptionAudioPlayback": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0437\u0432\u0443\u043a \u0437\u0430\u043f\u043e\u0447\u043d\u0430",
+    "NotificationOptionGamePlayback": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0438\u0433\u0440\u0430\u0442\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0430",
+    "NotificationOptionVideoPlaybackStopped": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0432\u0438\u0434\u0435\u043e \u0435 \u0441\u043f\u0440\u044f\u043d\u043e",
+    "NotificationOptionAudioPlaybackStopped": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0437\u0432\u0443\u043a \u0435 \u0441\u043f\u0440\u044f\u043d\u043e",
+    "NotificationOptionGamePlaybackStopped": "\u0412\u044a\u0437\u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0438\u0433\u0440\u0430\u0442\u0430 \u0435 \u0441\u043f\u0440\u044f\u043d\u0430",
+    "NotificationOptionTaskFailed": "\u0413\u0440\u0435\u0448\u043a\u0430 \u0432 \u043f\u043b\u0430\u043d\u0438\u0440\u0430\u043d\u0430 \u0437\u0430\u0434\u0430\u0447\u0430",
+    "NotificationOptionInstallationFailed": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0435",
+    "NotificationOptionNewLibraryContent": "\u0414\u043e\u0431\u0430\u0432\u0435\u043d\u043e \u0435 \u043d\u043e\u0432\u043e \u0441\u044a\u0434\u044a\u0440\u0436\u0430\u043d\u0438\u0435",
+    "NotificationOptionCameraImageUploaded": "\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u0442\u043e \u043e\u0442 \u0444\u043e\u0442\u043e\u0430\u043f\u0430\u0440\u0430\u0442\u0430 \u0435 \u043a\u0430\u0447\u0435\u043d\u043e",
     "NotificationOptionUserLockedOut": "User locked out",
     "NotificationOptionUserLockedOut": "User locked out",
     "NotificationOptionServerRestartRequired": "\u041d\u0443\u0436\u043d\u043e \u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u043f\u0443\u0441\u043a\u0430\u043d\u0435 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
     "NotificationOptionServerRestartRequired": "\u041d\u0443\u0436\u043d\u043e \u0435 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u043f\u0443\u0441\u043a\u0430\u043d\u0435 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
     "UserLockedOutWithName": "User {0} has been locked out",
     "UserLockedOutWithName": "User {0} has been locked out",
-    "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
+    "SubtitleDownloadFailureForItem": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0437\u0442\u0435\u0433\u043b\u044f\u043d\u0435 \u043d\u0430 \u0441\u0443\u0431\u0442\u0438\u0442\u0440\u0438 \u0437\u0430 {0}",
     "Sync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0430\u043d\u0435",
     "Sync": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u0430\u043d\u0435",
     "User": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b",
     "User": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b",
     "System": "\u0421\u0438\u0441\u0442\u0435\u043c\u0430",
     "System": "\u0421\u0438\u0441\u0442\u0435\u043c\u0430",
-    "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "\u041f\u043e\u0441\u0435\u0442\u0435\u0442\u0435 \u043e\u0431\u0449\u043d\u043e\u0441\u0442\u0442\u0430",
-    "LabelBrowseLibrary": "\u0420\u0430\u0437\u0433\u043b\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\u0442\u0430",
-    "LabelConfigureServer": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0415\u043c\u0431\u0438",
-    "LabelRestartServer": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e \u043f\u0443\u0441\u043a\u0430\u043d\u0435 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430"
+    "Application": "\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u0430",
+    "Plugin": "\u041f\u0440\u0438\u0441\u0442\u0430\u0432\u043a\u0430"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/ca.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 2 - 7
Emby.Server.Implementations/Localization/Core/cs.json

@@ -27,7 +27,7 @@
     "Artists": "Um\u011blci",
     "Artists": "Um\u011blci",
     "Folders": "Slo\u017eky",
     "Folders": "Slo\u017eky",
     "Songs": "Skladby",
     "Songs": "Skladby",
-    "TvShows": "TV Shows",
+    "TvShows": "TV seri\u00e1ly",
     "Shows": "Seri\u00e1ly",
     "Shows": "Seri\u00e1ly",
     "Genres": "\u017d\u00e1nry",
     "Genres": "\u017d\u00e1nry",
     "NameSeasonNumber": "Sez\u00f3na {0}",
     "NameSeasonNumber": "Sez\u00f3na {0}",
@@ -87,10 +87,5 @@
     "User": "U\u017eivatel",
     "User": "U\u017eivatel",
     "System": "Syst\u00e9m",
     "System": "Syst\u00e9m",
     "Application": "Aplikace",
     "Application": "Aplikace",
-    "Plugin": "Z\u00e1suvn\u00fd modul",
-    "LabelExit": "Uko\u010dit",
-    "LabelVisitCommunity": "Nav\u0161t\u00edvit komunitu",
-    "LabelBrowseLibrary": "Proch\u00e1zet knihovnu",
-    "LabelConfigureServer": "Konfigurovat Emby",
-    "LabelRestartServer": "Restartovat Server"
+    "Plugin": "Z\u00e1suvn\u00fd modul"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/da.json

@@ -87,10 +87,5 @@
     "User": "Bruger",
     "User": "Bruger",
     "System": "System",
     "System": "System",
     "Application": "Applikation",
     "Application": "Applikation",
-    "Plugin": "Plugin",
-    "LabelExit": "Afslut",
-    "LabelVisitCommunity": "Bes\u00f8g F\u00e6llesskab",
-    "LabelBrowseLibrary": "Gennemse Bibliotek",
-    "LabelConfigureServer": "Konfigurer Emby",
-    "LabelRestartServer": "Genstart Server"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/de.json

@@ -87,10 +87,5 @@
     "User": "Benutzer",
     "User": "Benutzer",
     "System": "System",
     "System": "System",
     "Application": "Anwendung",
     "Application": "Anwendung",
-    "Plugin": "Plugin",
-    "LabelExit": "Beenden",
-    "LabelVisitCommunity": "Besuche die Community",
-    "LabelBrowseLibrary": "Bibliothek durchsuchen",
-    "LabelConfigureServer": "Konfiguriere Emby",
-    "LabelRestartServer": "Server neustarten"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/en-GB.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/es-AR.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 2 - 7
Emby.Server.Implementations/Localization/Core/es-MX.json

@@ -27,7 +27,7 @@
     "Artists": "Artistas",
     "Artists": "Artistas",
     "Folders": "Carpetas",
     "Folders": "Carpetas",
     "Songs": "Canciones",
     "Songs": "Canciones",
-    "TvShows": "TV Shows",
+    "TvShows": "Programas de TV",
     "Shows": "Programas",
     "Shows": "Programas",
     "Genres": "G\u00e9neros",
     "Genres": "G\u00e9neros",
     "NameSeasonNumber": "Temporada {0}",
     "NameSeasonNumber": "Temporada {0}",
@@ -87,10 +87,5 @@
     "User": "Usuario",
     "User": "Usuario",
     "System": "Sistema",
     "System": "Sistema",
     "Application": "Aplicaci\u00f3n",
     "Application": "Aplicaci\u00f3n",
-    "Plugin": "Complemento",
-    "LabelExit": "Salir",
-    "LabelVisitCommunity": "Visitar la Comunidad",
-    "LabelBrowseLibrary": "Explorar Biblioteca",
-    "LabelConfigureServer": "Configurar Emby",
-    "LabelRestartServer": "Reiniciar el Servidor"
+    "Plugin": "Complemento"
 }
 }

+ 4 - 9
Emby.Server.Implementations/Localization/Core/es.json

@@ -27,7 +27,7 @@
     "Artists": "Artistas",
     "Artists": "Artistas",
     "Folders": "Carpetas",
     "Folders": "Carpetas",
     "Songs": "Canciones",
     "Songs": "Canciones",
-    "TvShows": "TV Shows",
+    "TvShows": "Series TV",
     "Shows": "Series",
     "Shows": "Series",
     "Genres": "G\u00e9neros",
     "Genres": "G\u00e9neros",
     "NameSeasonNumber": "Temporada {0}",
     "NameSeasonNumber": "Temporada {0}",
@@ -61,8 +61,8 @@
     "AuthenticationSucceededWithUserName": "{0} autenticado correctamente",
     "AuthenticationSucceededWithUserName": "{0} autenticado correctamente",
     "UserOfflineFromDevice": "{0} se ha desconectado de {1}",
     "UserOfflineFromDevice": "{0} se ha desconectado de {1}",
     "DeviceOfflineWithName": "{0} se ha desconectado",
     "DeviceOfflineWithName": "{0} se ha desconectado",
-    "UserStartedPlayingItemWithValues": "{0} ha comenzado jugando {1}",
-    "UserStoppedPlayingItemWithValues": "{0} ha dejado de reproducir {1}",
+    "UserStartedPlayingItemWithValues": "{0} ha comenzado reproducir {1}",
+    "UserStoppedPlayingItemWithValues": "{0} ha parado de reproducir {1}",
     "NotificationOptionPluginError": "Error en plugin",
     "NotificationOptionPluginError": "Error en plugin",
     "NotificationOptionApplicationUpdateAvailable": "Actualizaci\u00f3n de la aplicaci\u00f3n disponible",
     "NotificationOptionApplicationUpdateAvailable": "Actualizaci\u00f3n de la aplicaci\u00f3n disponible",
     "NotificationOptionApplicationUpdateInstalled": "Actualizaci\u00f3n de la aplicaci\u00f3n instalada",
     "NotificationOptionApplicationUpdateInstalled": "Actualizaci\u00f3n de la aplicaci\u00f3n instalada",
@@ -87,10 +87,5 @@
     "User": "Usuario",
     "User": "Usuario",
     "System": "Sistema",
     "System": "Sistema",
     "Application": "Aplicaci\u00f3n",
     "Application": "Aplicaci\u00f3n",
-    "Plugin": "Plugin",
-    "LabelExit": "Salida",
-    "LabelVisitCommunity": "Visita la Comunidad",
-    "LabelBrowseLibrary": "Navegar la biblioteca",
-    "LabelConfigureServer": "Configurar Emby",
-    "LabelRestartServer": "Configurar Emby"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/fr-CA.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 2 - 7
Emby.Server.Implementations/Localization/Core/fr.json

@@ -27,7 +27,7 @@
     "Artists": "Artistes",
     "Artists": "Artistes",
     "Folders": "Dossiers",
     "Folders": "Dossiers",
     "Songs": "Chansons",
     "Songs": "Chansons",
-    "TvShows": "TV Shows",
+    "TvShows": "S\u00e9ries T\u00e9l\u00e9",
     "Shows": "\u00c9missions",
     "Shows": "\u00c9missions",
     "Genres": "Genres",
     "Genres": "Genres",
     "NameSeasonNumber": "Saison {0}",
     "NameSeasonNumber": "Saison {0}",
@@ -87,10 +87,5 @@
     "User": "Utilisateur",
     "User": "Utilisateur",
     "System": "Syst\u00e8me",
     "System": "Syst\u00e8me",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Extension",
-    "LabelExit": "Quitter",
-    "LabelVisitCommunity": "Visiter la communaut\u00e9",
-    "LabelBrowseLibrary": "Parcourir la m\u00e9diath\u00e8que",
-    "LabelConfigureServer": "Configurer Emby",
-    "LabelRestartServer": "Red\u00e9marrer le serveur"
+    "Plugin": "Extension"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/he.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/hr.json

@@ -87,10 +87,5 @@
     "User": "Korisnik",
     "User": "Korisnik",
     "System": "Sistem",
     "System": "Sistem",
     "Application": "Aplikacija",
     "Application": "Aplikacija",
-    "Plugin": "Dodatak",
-    "LabelExit": "Izlaz",
-    "LabelVisitCommunity": "Posjeti zajednicu",
-    "LabelBrowseLibrary": "Pregledaj biblioteku",
-    "LabelConfigureServer": "Podesi Emby",
-    "LabelRestartServer": "Restartiraj Server"
+    "Plugin": "Dodatak"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/hu.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Kil\u00e9p\u00e9s",
-    "LabelVisitCommunity": "K\u00f6z\u00f6ss\u00e9g",
-    "LabelBrowseLibrary": "M\u00e9diat\u00e1r tall\u00f3z\u00e1sa",
-    "LabelConfigureServer": "Emby konfigur\u00e1l\u00e1sa",
-    "LabelRestartServer": "Szerver \u00fajraindit\u00e1sa"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/it.json

@@ -87,10 +87,5 @@
     "User": "Utente",
     "User": "Utente",
     "System": "Sistema",
     "System": "Sistema",
     "Application": "Applicazione",
     "Application": "Applicazione",
-    "Plugin": "Plug-in",
-    "LabelExit": "Esci",
-    "LabelVisitCommunity": "Visita il forum di discussione",
-    "LabelBrowseLibrary": "Esplora la libreria",
-    "LabelConfigureServer": "Configura Emby",
-    "LabelRestartServer": "Riavvia Server"
+    "Plugin": "Plug-in"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/kk.json

@@ -87,10 +87,5 @@
     "User": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b",
     "User": "\u041f\u0430\u0439\u0434\u0430\u043b\u0430\u043d\u0443\u0448\u044b",
     "System": "\u0416\u04af\u0439\u0435",
     "System": "\u0416\u04af\u0439\u0435",
     "Application": "\u049a\u043e\u043b\u0434\u0430\u043d\u0431\u0430",
     "Application": "\u049a\u043e\u043b\u0434\u0430\u043d\u0431\u0430",
-    "Plugin": "\u041f\u043b\u0430\u0433\u0438\u043d",
-    "LabelExit": "\u0428\u044b\u0493\u0443",
-    "LabelVisitCommunity": "\u049a\u0430\u0443\u044b\u043c\u0434\u0430\u0441\u0442\u044b\u049b\u049b\u0430 \u0431\u0430\u0440\u0443",
-    "LabelBrowseLibrary": "\u0422\u0430\u0441\u044b\u0493\u044b\u0448\u0445\u0430\u043d\u0430\u043d\u044b \u0448\u043e\u043b\u0443",
-    "LabelConfigureServer": "Emby \u0442\u0435\u04a3\u0448\u0435\u0443",
-    "LabelRestartServer": "\u0421\u0435\u0440\u0432\u0435\u0440\u0434\u0456 \u049b\u0430\u0439\u0442\u0430 \u0456\u0441\u043a\u0435 \u049b\u043e\u0441\u0443"
+    "Plugin": "\u041f\u043b\u0430\u0433\u0438\u043d"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/ko.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/lt-LT.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/ms.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/nb.json

@@ -87,10 +87,5 @@
     "User": "Bruker",
     "User": "Bruker",
     "System": "System",
     "System": "System",
     "Application": "Applikasjon",
     "Application": "Applikasjon",
-    "Plugin": "Plugin",
-    "LabelExit": "Avslutt",
-    "LabelVisitCommunity": "Bes\u00f8k Samfunnet",
-    "LabelBrowseLibrary": "Bla i biblioteket",
-    "LabelConfigureServer": "Konfigurere Emby",
-    "LabelRestartServer": "Start om serveren"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/nl.json

@@ -87,10 +87,5 @@
     "User": "Gebruiker",
     "User": "Gebruiker",
     "System": "Systeem",
     "System": "Systeem",
     "Application": "Toepassing",
     "Application": "Toepassing",
-    "Plugin": "Plug-in",
-    "LabelExit": "Afsluiten",
-    "LabelVisitCommunity": "Bezoek Gemeenschap",
-    "LabelBrowseLibrary": "Bekijk bibliotheek",
-    "LabelConfigureServer": "Emby Configureren",
-    "LabelRestartServer": "Server herstarten"
+    "Plugin": "Plug-in"
 }
 }

+ 2 - 7
Emby.Server.Implementations/Localization/Core/pl.json

@@ -27,7 +27,7 @@
     "Artists": "Wykonawcy",
     "Artists": "Wykonawcy",
     "Folders": "Foldery",
     "Folders": "Foldery",
     "Songs": "Utwory",
     "Songs": "Utwory",
-    "TvShows": "TV Shows",
+    "TvShows": "Seriale",
     "Shows": "Seriale",
     "Shows": "Seriale",
     "Genres": "Gatunki",
     "Genres": "Gatunki",
     "NameSeasonNumber": "Sezon {0}",
     "NameSeasonNumber": "Sezon {0}",
@@ -87,10 +87,5 @@
     "User": "U\u017cytkownik",
     "User": "U\u017cytkownik",
     "System": "System",
     "System": "System",
     "Application": "Aplikacja",
     "Application": "Aplikacja",
-    "Plugin": "Wtyczka",
-    "LabelExit": "Wyj\u015bcie",
-    "LabelVisitCommunity": "Odwied\u017a spo\u0142eczno\u015b\u0107",
-    "LabelBrowseLibrary": "Przegl\u0105daj bibliotek\u0119",
-    "LabelConfigureServer": "Konfiguracja Emby",
-    "LabelRestartServer": "Uruchom serwer ponownie"
+    "Plugin": "Wtyczka"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/pt-BR.json

@@ -87,10 +87,5 @@
     "User": "Usu\u00e1rio",
     "User": "Usu\u00e1rio",
     "System": "Sistema",
     "System": "Sistema",
     "Application": "Aplicativo",
     "Application": "Aplicativo",
-    "Plugin": "Plugin",
-    "LabelExit": "Sair",
-    "LabelVisitCommunity": "Visite a Comunidade",
-    "LabelBrowseLibrary": "Explorar Biblioteca",
-    "LabelConfigureServer": "Configurar Emby",
-    "LabelRestartServer": "Reiniciar Servidor"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/pt-PT.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Aplica\u00e7\u00e3o",
     "Application": "Aplica\u00e7\u00e3o",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/ru.json

@@ -87,10 +87,5 @@
     "User": "\u041f\u043e\u043b\u044c\u0437-\u043b\u044c",
     "User": "\u041f\u043e\u043b\u044c\u0437-\u043b\u044c",
     "System": "\u0421\u0438\u0441\u0442\u0435\u043c\u0430",
     "System": "\u0421\u0438\u0441\u0442\u0435\u043c\u0430",
     "Application": "\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435",
     "Application": "\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435",
-    "Plugin": "\u041f\u043b\u0430\u0433\u0438\u043d",
-    "LabelExit": "\u0412\u044b\u0445\u043e\u0434",
-    "LabelVisitCommunity": "\u041f\u043e\u0441\u0435\u0449\u0435\u043d\u0438\u0435 \u0421\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430",
-    "LabelBrowseLibrary": "\u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u043f\u043e \u043c\u0435\u0434\u0438\u0430\u0442\u0435\u043a\u0435",
-    "LabelConfigureServer": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Emby",
-    "LabelRestartServer": "\u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0430"
+    "Plugin": "\u041f\u043b\u0430\u0433\u0438\u043d"
 }
 }

+ 69 - 74
Emby.Server.Implementations/Localization/Core/sk.json

@@ -1,96 +1,91 @@
 {
 {
-    "Latest": "Latest",
-    "ValueSpecialEpisodeName": "Special - {0}",
-    "Inherit": "Inherit",
-    "Books": "Books",
-    "Music": "Music",
-    "Games": "Games",
-    "Photos": "Photos",
-    "MixedContent": "Mixed content",
-    "MusicVideos": "Music videos",
-    "HomeVideos": "Home videos",
-    "Playlists": "Playlists",
+    "Latest": "Najnov\u0161ie",
+    "ValueSpecialEpisodeName": "\u0160peci\u00e1l - {0}",
+    "Inherit": "Zdedi\u0165",
+    "Books": "Knihy",
+    "Music": "Hudba",
+    "Games": "Hry",
+    "Photos": "Fotky",
+    "MixedContent": "Zmie\u0161an\u00fd obsah",
+    "MusicVideos": "Hudobn\u00e9 vide\u00e1",
+    "HomeVideos": "Dom\u00e1ce vide\u00e1",
+    "Playlists": "Zoznamy skladieb",
     "HeaderRecordingGroups": "Recording Groups",
     "HeaderRecordingGroups": "Recording Groups",
-    "HeaderContinueWatching": "Continue Watching",
-    "HeaderFavoriteArtists": "Favorite Artists",
+    "HeaderContinueWatching": "Pokra\u010dujte v pozeran\u00ed",
+    "HeaderFavoriteArtists": "Ob\u013e\u00faben\u00ed umelci",
     "HeaderFavoriteSongs": "Ob\u013e\u00faben\u00e9 pesni\u010dky",
     "HeaderFavoriteSongs": "Ob\u013e\u00faben\u00e9 pesni\u010dky",
     "HeaderAlbumArtists": "Album Artists",
     "HeaderAlbumArtists": "Album Artists",
-    "HeaderFavoriteAlbums": "Favorite Albums",
-    "HeaderFavoriteEpisodes": "Favorite Episodes",
+    "HeaderFavoriteAlbums": "Ob\u013e\u00faben\u00e9 albumy",
+    "HeaderFavoriteEpisodes": "Ob\u013e\u00faben\u00e9 epiz\u00f3dy",
     "HeaderFavoriteShows": "Ob\u013e\u00faben\u00e9 seri\u00e1ly",
     "HeaderFavoriteShows": "Ob\u013e\u00faben\u00e9 seri\u00e1ly",
-    "HeaderNextUp": "Next Up",
+    "HeaderNextUp": "Nasleduje",
     "Favorites": "Ob\u013e\u00faben\u00e9",
     "Favorites": "Ob\u013e\u00faben\u00e9",
-    "Collections": "Collections",
-    "Channels": "Channels",
-    "Movies": "Movies",
-    "Albums": "Albums",
-    "Artists": "Artists",
-    "Folders": "Folders",
-    "Songs": "Songs",
+    "Collections": "Zbierky",
+    "Channels": "Kan\u00e1ly",
+    "Movies": "Filmy",
+    "Albums": "Albumy",
+    "Artists": "Umelci",
+    "Folders": "Prie\u010dinky",
+    "Songs": "Skladby",
     "TvShows": "TV Shows",
     "TvShows": "TV Shows",
     "Shows": "Series",
     "Shows": "Series",
-    "Genres": "Genres",
-    "NameSeasonNumber": "Season {0}",
-    "AppDeviceValues": "App: {0}, Device: {1}",
-    "UserDownloadingItemWithValues": "{0} is downloading {1}",
+    "Genres": "\u017d\u00e1nre",
+    "NameSeasonNumber": "Sez\u00f3na {0}",
+    "AppDeviceValues": "Aplik\u00e1cia: {0}, Zariadenie: {1}",
+    "UserDownloadingItemWithValues": "{0} s\u0165ahuje {1}",
     "HeaderLiveTV": "Live TV",
     "HeaderLiveTV": "Live TV",
-    "ChapterNameValue": "Chapter {0}",
-    "ScheduledTaskFailedWithName": "{0} failed",
-    "LabelRunningTimeValue": "Running time: {0}",
+    "ChapterNameValue": "Kapitola {0}",
+    "ScheduledTaskFailedWithName": "{0} zlyhalo",
+    "LabelRunningTimeValue": "D\u013a\u017eka: {0}",
     "ScheduledTaskStartedWithName": "{0} started",
     "ScheduledTaskStartedWithName": "{0} started",
-    "VersionNumber": "Version {0}",
-    "PluginInstalledWithName": "{0} was installed",
-    "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly.",
-    "PluginUpdatedWithName": "{0} was updated",
-    "PluginUninstalledWithName": "{0} was uninstalled",
-    "ItemAddedWithName": "{0} was added to the library",
-    "ItemRemovedWithName": "{0} was removed from the library",
-    "LabelIpAddressValue": "Ip address: {0}",
-    "DeviceOnlineWithName": "{0} is connected",
-    "UserOnlineFromDevice": "{0} is online from {1}",
+    "VersionNumber": "Verzia {0}",
+    "PluginInstalledWithName": "{0} bol nain\u0161talovan\u00fd",
+    "StartupEmbyServerIsLoading": "Emby Server sa sp\u00fa\u0161\u0165a. Sk\u00faste to pros\u00edm o chv\u00ed\u013eu znova.",
+    "PluginUpdatedWithName": "{0} bol aktualizovan\u00fd",
+    "PluginUninstalledWithName": "{0} bol odin\u0161talovan\u00fd",
+    "ItemAddedWithName": "{0} bol pridan\u00fd do kni\u017enice",
+    "ItemRemovedWithName": "{0} bol odstr\u00e1nen\u00fd z kni\u017enice",
+    "LabelIpAddressValue": "IP adresa: {0}",
+    "DeviceOnlineWithName": "{0} je pripojen\u00fd",
+    "UserOnlineFromDevice": "{0} je online z {1}",
     "ProviderValue": "Provider: {0}",
     "ProviderValue": "Provider: {0}",
-    "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
-    "UserCreatedWithName": "User {0} has been created",
-    "UserPasswordChangedWithName": "Password has been changed for user {0}",
-    "UserDeletedWithName": "User {0} has been deleted",
+    "SubtitlesDownloadedForItem": "Titulky pre {0} stiahnut\u00e9",
+    "UserCreatedWithName": "Pou\u017e\u00edvate\u013e {0} bol vytvoren\u00fd",
+    "UserPasswordChangedWithName": "Heslo pou\u017e\u00edvate\u013ea {0} zmenen\u00e9",
+    "UserDeletedWithName": "Pou\u017e\u00edvate\u013e {0} bol vymazan\u00fd",
     "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
     "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
-    "MessageServerConfigurationUpdated": "Server configuration has been updated",
-    "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
-    "MessageApplicationUpdated": "Emby Server has been updated",
-    "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
-    "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
-    "UserOfflineFromDevice": "{0} has disconnected from {1}",
-    "DeviceOfflineWithName": "{0} has disconnected",
-    "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
-    "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
+    "MessageServerConfigurationUpdated": "Konfigur\u00e1cia servera aktualizovan\u00e1",
+    "MessageNamedServerConfigurationUpdatedWithValue": "Sekcia {0} konfigur\u00e1cie servera bola aktualizovan\u00e1",
+    "MessageApplicationUpdated": "Emby Server bol aktualizovan\u00fd",
+    "FailedLoginAttemptWithUserName": "Ne\u00faspe\u0161n\u00fd pokus o prihl\u00e1senie z {0}",
+    "AuthenticationSucceededWithUserName": "{0} \u00faspe\u0161ne overen\u00fd",
+    "UserOfflineFromDevice": "{0} sa odpojil od {1}",
+    "DeviceOfflineWithName": "{0} je odpojen\u00fd",
+    "UserStartedPlayingItemWithValues": "{0} spustil prehr\u00e1vanie {1}",
+    "UserStoppedPlayingItemWithValues": "{0} zastavil prehr\u00e1vanie {1}",
     "NotificationOptionPluginError": "Plugin failure",
     "NotificationOptionPluginError": "Plugin failure",
-    "NotificationOptionApplicationUpdateAvailable": "Application update available",
-    "NotificationOptionApplicationUpdateInstalled": "Application update installed",
+    "NotificationOptionApplicationUpdateAvailable": "Je dostupn\u00e1 aktualiz\u00e1cia aplik\u00e1cie",
+    "NotificationOptionApplicationUpdateInstalled": "Aktualiz\u00e1cia aplik\u00e1cie nain\u0161talovan\u00e1",
     "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
     "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
     "NotificationOptionPluginInstalled": "Plugin installed",
     "NotificationOptionPluginInstalled": "Plugin installed",
     "NotificationOptionPluginUninstalled": "Plugin uninstalled",
     "NotificationOptionPluginUninstalled": "Plugin uninstalled",
-    "NotificationOptionVideoPlayback": "Video playback started",
-    "NotificationOptionAudioPlayback": "Audio playback started",
+    "NotificationOptionVideoPlayback": "Spusten\u00e9 prehr\u00e1vanie videa",
+    "NotificationOptionAudioPlayback": "Spusten\u00e9 prehr\u00e1vanie audia",
     "NotificationOptionGamePlayback": "Game playback started",
     "NotificationOptionGamePlayback": "Game playback started",
-    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
-    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
-    "NotificationOptionGamePlaybackStopped": "Game playback stopped",
-    "NotificationOptionTaskFailed": "Scheduled task failure",
-    "NotificationOptionInstallationFailed": "Installation failure",
-    "NotificationOptionNewLibraryContent": "New content added",
+    "NotificationOptionVideoPlaybackStopped": "Zastaven\u00e9 prehr\u00e1vanie videa",
+    "NotificationOptionAudioPlaybackStopped": "Zastaven\u00e9 prehr\u00e1vanie audia",
+    "NotificationOptionGamePlaybackStopped": "Hra ukon\u010den\u00e1",
+    "NotificationOptionTaskFailed": "Napl\u00e1novan\u00e1 \u00faloha zlyhala",
+    "NotificationOptionInstallationFailed": "Chyba in\u0161tal\u00e1cie",
+    "NotificationOptionNewLibraryContent": "Pridan\u00fd nov\u00fd obsah",
     "NotificationOptionCameraImageUploaded": "Camera image uploaded",
     "NotificationOptionCameraImageUploaded": "Camera image uploaded",
     "NotificationOptionUserLockedOut": "User locked out",
     "NotificationOptionUserLockedOut": "User locked out",
-    "NotificationOptionServerRestartRequired": "Server restart required",
+    "NotificationOptionServerRestartRequired": "Vy\u017eaduje sa re\u0161tart servera",
     "UserLockedOutWithName": "User {0} has been locked out",
     "UserLockedOutWithName": "User {0} has been locked out",
-    "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
+    "SubtitleDownloadFailureForItem": "S\u0165ahovanie titulkov pre {0} zlyhalo",
     "Sync": "Sync",
     "Sync": "Sync",
-    "User": "User",
-    "System": "System",
-    "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "User": "Pou\u017e\u00edvate\u013e",
+    "System": "Syst\u00e9m",
+    "Application": "Aplik\u00e1cia",
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/sl-SI.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 2 - 7
Emby.Server.Implementations/Localization/Core/sv.json

@@ -27,7 +27,7 @@
     "Artists": "Artister",
     "Artists": "Artister",
     "Folders": "Mappar",
     "Folders": "Mappar",
     "Songs": "L\u00e5tar",
     "Songs": "L\u00e5tar",
-    "TvShows": "TV Shows",
+    "TvShows": "TV-serier",
     "Shows": "Serier",
     "Shows": "Serier",
     "Genres": "Genrer",
     "Genres": "Genrer",
     "NameSeasonNumber": "S\u00e4song {0}",
     "NameSeasonNumber": "S\u00e4song {0}",
@@ -87,10 +87,5 @@
     "User": "Anv\u00e4ndare",
     "User": "Anv\u00e4ndare",
     "System": "System",
     "System": "System",
     "Application": "App",
     "Application": "App",
-    "Plugin": "Till\u00e4gg",
-    "LabelExit": "Avsluta",
-    "LabelVisitCommunity": "Bes\u00f6k v\u00e5rt diskussionsforum",
-    "LabelBrowseLibrary": "Bl\u00e4ddra i biblioteket",
-    "LabelConfigureServer": "Konfigurera Emby",
-    "LabelRestartServer": "Starta om servern"
+    "Plugin": "Till\u00e4gg"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/tr.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "Visit Community",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/zh-CN.json

@@ -87,10 +87,5 @@
     "User": "\u7528\u6237",
     "User": "\u7528\u6237",
     "System": "\u7cfb\u7edf",
     "System": "\u7cfb\u7edf",
     "Application": "\u5e94\u7528\u7a0b\u5e8f",
     "Application": "\u5e94\u7528\u7a0b\u5e8f",
-    "Plugin": "\u63d2\u4ef6",
-    "LabelExit": "\u9000\u51fa",
-    "LabelVisitCommunity": "\u8bbf\u95ee\u793e\u533a",
-    "LabelBrowseLibrary": "\u6d4f\u89c8\u5a92\u4f53\u5e93",
-    "LabelConfigureServer": "\u914d\u7f6e Emby",
-    "LabelRestartServer": "\u91cd\u542f\u670d\u52a1\u5668"
+    "Plugin": "\u63d2\u4ef6"
 }
 }

+ 1 - 6
Emby.Server.Implementations/Localization/Core/zh-HK.json

@@ -87,10 +87,5 @@
     "User": "User",
     "User": "User",
     "System": "System",
     "System": "System",
     "Application": "Application",
     "Application": "Application",
-    "Plugin": "Plugin",
-    "LabelExit": "Exit",
-    "LabelVisitCommunity": "\u8a2a\u554f\u8a0e\u8ad6\u5340",
-    "LabelBrowseLibrary": "Browse Library",
-    "LabelConfigureServer": "Configure Emby",
-    "LabelRestartServer": "Restart Server"
+    "Plugin": "Plugin"
 }
 }

+ 20 - 8
Emby.Server.Implementations/Localization/LocalizationManager.cs

@@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Localization
 
 
             var localizationPath = LocalizationPath;
             var localizationPath = LocalizationPath;
 
 
-			_fileSystem.CreateDirectory(localizationPath);
+            _fileSystem.CreateDirectory(localizationPath);
 
 
             var existingFiles = GetRatingsFiles(localizationPath)
             var existingFiles = GetRatingsFiles(localizationPath)
                 .Select(Path.GetFileName)
                 .Select(Path.GetFileName)
@@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.Localization
                     }
                     }
                 }
                 }
             }
             }
-            
+
             foreach (var file in GetRatingsFiles(localizationPath))
             foreach (var file in GetRatingsFiles(localizationPath))
             {
             {
                 LoadRatings(file);
                 LoadRatings(file);
@@ -128,12 +128,20 @@ namespace Emby.Server.Implementations.Localization
             return _textLocalizer.NormalizeFormKD(text);
             return _textLocalizer.NormalizeFormKD(text);
         }
         }
 
 
+        private CultureDto[] _cultures;
+
         /// <summary>
         /// <summary>
         /// Gets the cultures.
         /// Gets the cultures.
         /// </summary>
         /// </summary>
         /// <returns>IEnumerable{CultureDto}.</returns>
         /// <returns>IEnumerable{CultureDto}.</returns>
         public CultureDto[] GetCultures()
         public CultureDto[] GetCultures()
         {
         {
+            var result = _cultures;
+            if (result != null)
+            {
+                return result;
+            }
+
             var type = GetType();
             var type = GetType();
             var path = type.Namespace + ".iso6392.txt";
             var path = type.Namespace + ".iso6392.txt";
 
 
@@ -166,10 +174,14 @@ namespace Emby.Server.Implementations.Localization
                 }
                 }
             }
             }
 
 
-            return list.Where(i => !string.IsNullOrWhiteSpace(i.Name) &&
-                !string.IsNullOrWhiteSpace(i.DisplayName) &&
-                !string.IsNullOrWhiteSpace(i.ThreeLetterISOLanguageName) &&
-                !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToArray();
+            result = list.Where(i => !string.IsNullOrWhiteSpace(i.Name) &&
+               !string.IsNullOrWhiteSpace(i.DisplayName) &&
+               !string.IsNullOrWhiteSpace(i.ThreeLetterISOLanguageName) &&
+               !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToArray();
+
+            _cultures = result;
+
+            return result;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -239,7 +251,7 @@ namespace Emby.Server.Implementations.Localization
         /// <returns>Dictionary{System.StringParentalRating}.</returns>
         /// <returns>Dictionary{System.StringParentalRating}.</returns>
         private void LoadRatings(string file)
         private void LoadRatings(string file)
         {
         {
-			var dict = _fileSystem.ReadAllLines(file).Select(i =>
+            var dict = _fileSystem.ReadAllLines(file).Select(i =>
             {
             {
                 if (!string.IsNullOrWhiteSpace(i))
                 if (!string.IsNullOrWhiteSpace(i))
                 {
                 {
@@ -269,7 +281,7 @@ namespace Emby.Server.Implementations.Localization
             _allParentalRatings.TryAdd(countryCode, dict);
             _allParentalRatings.TryAdd(countryCode, dict);
         }
         }
 
 
-        private readonly string[] _unratedValues = {"n/a", "unrated", "not rated"};
+        private readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
 
 
         /// <summary>
         /// <summary>
         /// Gets the rating level.
         /// Gets the rating level.

+ 5 - 4
Emby.Server.Implementations/MediaEncoder/EncodingManager.cs

@@ -17,6 +17,7 @@ using System.Threading.Tasks;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 
 
 namespace Emby.Server.Implementations.MediaEncoder
 namespace Emby.Server.Implementations.MediaEncoder
 {
 {
@@ -89,7 +90,7 @@ namespace Emby.Server.Implementations.MediaEncoder
         /// </summary>
         /// </summary>
         private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
         private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
 
 
-        public async Task<bool> RefreshChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
+        public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
         {
         {
             if (!IsEligibleForChapterImageExtraction(video))
             if (!IsEligibleForChapterImageExtraction(video))
             {
             {
@@ -101,7 +102,7 @@ namespace Emby.Server.Implementations.MediaEncoder
 
 
             var runtimeTicks = video.RunTimeTicks ?? 0;
             var runtimeTicks = video.RunTimeTicks ?? 0;
 
 
-            var currentImages = GetSavedChapterImages(video);
+            var currentImages = GetSavedChapterImages(video, directoryService);
 
 
             foreach (var chapter in chapters)
             foreach (var chapter in chapters)
             {
             {
@@ -194,13 +195,13 @@ namespace Emby.Server.Implementations.MediaEncoder
             return Path.Combine(GetChapterImagesPath(video), filename);
             return Path.Combine(GetChapterImagesPath(video), filename);
         }
         }
 
 
-        private List<string> GetSavedChapterImages(Video video)
+        private List<string> GetSavedChapterImages(Video video, IDirectoryService directoryService)
         {
         {
             var path = GetChapterImagesPath(video);
             var path = GetChapterImagesPath(video);
 
 
             try
             try
             {
             {
-                return _fileSystem.GetFilePaths(path)
+                return directoryService.GetFilePaths(path)
                     .ToList();
                     .ToList();
             }
             }
             catch (IOException)
             catch (IOException)

+ 11 - 1
Emby.Server.Implementations/Net/SocketFactory.cs

@@ -196,8 +196,18 @@ namespace Emby.Server.Implementations.Net
 
 
             try
             try
             {
             {
-                //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
+                // seeing occasional exceptions thrown on qnap
+                // System.Net.Sockets.SocketException (0x80004005): Protocol not available
                 retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                 retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+            }
+            catch (SocketException)
+            {
+
+            }
+
+            try
+            {
+                //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
                 retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
                 retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
 
 
                 var localIp = IPAddress.Any;
                 var localIp = IPAddress.Any;

+ 13 - 7
Emby.Server.Implementations/Networking/NetworkManager.cs

@@ -59,7 +59,13 @@ namespace Emby.Server.Implementations.Networking
                 list.AddRange(GetLocalIpAddressesFallback().Result);
                 list.AddRange(GetLocalIpAddressesFallback().Result);
             }
             }
 
 
-            return list.Where(FilterIpAddress).DistinctBy(i => i.ToString());
+            var listClone = list.ToList();
+
+            return list
+                .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1)
+                .ThenBy(i => listClone.IndexOf(i))
+                .Where(FilterIpAddress)
+                .DistinctBy(i => i.ToString());
         }
         }
 
 
         private bool FilterIpAddress(IPAddress address)
         private bool FilterIpAddress(IPAddress address)
@@ -106,16 +112,16 @@ namespace Emby.Server.Implementations.Networking
                 endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
                 endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) ||
                 endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase) ||
                 endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase) ||
                 endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase) ||
                 endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase) ||
-                endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) ||
+                //endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase) ||
                 IsInPrivateAddressSpaceAndLocalSubnet(endpoint);
                 IsInPrivateAddressSpaceAndLocalSubnet(endpoint);
         }
         }
 
 
         public bool IsInPrivateAddressSpaceAndLocalSubnet(string endpoint)
         public bool IsInPrivateAddressSpaceAndLocalSubnet(string endpoint)
         {
         {
-            var endpointFirstPart = endpoint.Split('.')[0];
-
             if (endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase))
             if (endpoint.StartsWith("10.", StringComparison.OrdinalIgnoreCase))
             {
             {
+                var endpointFirstPart = endpoint.Split('.')[0];
+
                 var subnets = GetSubnets(endpointFirstPart);
                 var subnets = GetSubnets(endpointFirstPart);
 
 
                 foreach (var subnet_Match in subnets)
                 foreach (var subnet_Match in subnets)
@@ -217,7 +223,7 @@ namespace Emby.Server.Implementations.Networking
                 }
                 }
                 else if (address.AddressFamily == AddressFamily.InterNetworkV6)
                 else if (address.AddressFamily == AddressFamily.InterNetworkV6)
                 {
                 {
-                    lengthMatch = 10;
+                    lengthMatch = 9;
                     if (IsInPrivateAddressSpace(endpoint))
                     if (IsInPrivateAddressSpace(endpoint))
                     {
                     {
                         return true;
                         return true;
@@ -317,7 +323,7 @@ namespace Emby.Server.Implementations.Networking
                     return ipProperties.UnicastAddresses
                     return ipProperties.UnicastAddresses
                         //.Where(i => i.IsDnsEligible)
                         //.Where(i => i.IsDnsEligible)
                         .Select(i => i.Address)
                         .Select(i => i.Address)
-                        .Where(i => i.AddressFamily == AddressFamily.InterNetwork)
+                        .Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6)
                         .ToList();
                         .ToList();
                 }
                 }
                 catch (Exception ex)
                 catch (Exception ex)
@@ -337,7 +343,7 @@ namespace Emby.Server.Implementations.Networking
             // Reverse them because the last one is usually the correct one
             // Reverse them because the last one is usually the correct one
             // It's not fool-proof so ultimately the consumer will have to examine them and decide
             // It's not fool-proof so ultimately the consumer will have to examine them and decide
             return host.AddressList
             return host.AddressList
-                .Where(i => i.AddressFamily == AddressFamily.InterNetwork)
+                .Where(i => i.AddressFamily == AddressFamily.InterNetwork || i.AddressFamily == AddressFamily.InterNetworkV6)
                 .Reverse();
                 .Reverse();
         }
         }
 
 

+ 4 - 1
Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs

@@ -16,6 +16,7 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Extensions;
+using MediaBrowser.Controller.Providers;
 
 
 namespace Emby.Server.Implementations.ScheduledTasks
 namespace Emby.Server.Implementations.ScheduledTasks
 {
 {
@@ -120,6 +121,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 previouslyFailedImages = new List<string>();
                 previouslyFailedImages = new List<string>();
             }
             }
 
 
+            var directoryService = new DirectoryService(_fileSystem);
+
             foreach (var video in videos)
             foreach (var video in videos)
             {
             {
                 cancellationToken.ThrowIfCancellationRequested();
                 cancellationToken.ThrowIfCancellationRequested();
@@ -132,7 +135,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 {
                 {
                     var chapters = _itemRepo.GetChapters(video.Id);
                     var chapters = _itemRepo.GetChapters(video.Id);
 
 
-                    var success = await _encodingManager.RefreshChapterImages(video, chapters, extract, true, CancellationToken.None);
+                    var success = await _encodingManager.RefreshChapterImages(video, directoryService, chapters, extract, true, CancellationToken.None);
 
 
                     if (!success)
                     if (!success)
                     {
                     {

+ 2 - 0
Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -464,6 +464,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// <param name="e">The e.</param>
         /// <param name="e">The e.</param>
         void progress_ProgressChanged(object sender, double e)
         void progress_ProgressChanged(object sender, double e)
         {
         {
+            e = Math.Min(e, 100);
+
             CurrentProgress = e;
             CurrentProgress = e;
 
 
             EventHelper.FireEventIfNotNull(TaskProgress, this, new GenericEventArgs<double>
             EventHelper.FireEventIfNotNull(TaskProgress, this, new GenericEventArgs<double>

+ 12 - 0
Emby.Server.Implementations/Session/HttpSessionController.cs

@@ -109,6 +109,18 @@ namespace Emby.Server.Implementations.Session
             {
             {
                 dict["StartPositionTicks"] = command.StartPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
                 dict["StartPositionTicks"] = command.StartPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
             }
             }
+            if (command.AudioStreamIndex.HasValue)
+            {
+                dict["AudioStreamIndex"] = command.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture);
+            }
+            if (command.SubtitleStreamIndex.HasValue)
+            {
+                dict["SubtitleStreamIndex"] = command.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture);
+            }
+            if (!string.IsNullOrWhiteSpace(command.MediaSourceId))
+            {
+                dict["MediaSourceId"] = command.MediaSourceId;
+            }
 
 
             return SendMessage(command.PlayCommand.ToString(), dict, cancellationToken);
             return SendMessage(command.PlayCommand.ToString(), dict, cancellationToken);
         }
         }

+ 5 - 6
Emby.Server.Implementations/Session/SessionManager.cs

@@ -972,7 +972,6 @@ namespace Emby.Server.Implementations.Session
             if (command.PlayCommand == PlayCommand.PlayInstantMix)
             if (command.PlayCommand == PlayCommand.PlayInstantMix)
             {
             {
                 items = command.ItemIds.SelectMany(i => TranslateItemForInstantMix(i, user))
                 items = command.ItemIds.SelectMany(i => TranslateItemForInstantMix(i, user))
-                    .Where(i => i.LocationType != LocationType.Virtual)
                     .ToList();
                     .ToList();
 
 
                 command.PlayCommand = PlayCommand.PlayNow;
                 command.PlayCommand = PlayCommand.PlayNow;
@@ -986,9 +985,7 @@ namespace Emby.Server.Implementations.Session
                     list.AddRange(subItems);
                     list.AddRange(subItems);
                 }
                 }
 
 
-                items = list
-                   .Where(i => i.LocationType != LocationType.Virtual)
-                   .ToList();
+                items = list;
             }
             }
 
 
             if (command.PlayCommand == PlayCommand.PlayShuffle)
             if (command.PlayCommand == PlayCommand.PlayShuffle)
@@ -1074,7 +1071,8 @@ namespace Emby.Server.Implementations.Session
                         {
                         {
                             ItemFields.SortName
                             ItemFields.SortName
                         }
                         }
-                    }
+                    },
+                    IsVirtualItem = false
                 });
                 });
 
 
                 return FilterToSingleMediaType(items)
                 return FilterToSingleMediaType(items)
@@ -1097,7 +1095,8 @@ namespace Emby.Server.Implementations.Session
                         {
                         {
                             ItemFields.SortName
                             ItemFields.SortName
                         }
                         }
-                    }
+                    },
+                    IsVirtualItem = false
 
 
                 });
                 });
 
 

+ 10 - 5
MediaBrowser.Api/Library/LibraryService.cs

@@ -335,7 +335,8 @@ namespace MediaBrowser.Api.Library
                     Fields = request.Fields,
                     Fields = request.Fields,
                     Id = request.Id,
                     Id = request.Id,
                     Limit = request.Limit,
                     Limit = request.Limit,
-                    UserId = request.UserId
+                    UserId = request.UserId,
+                    ImageTypeLimit = request.ImageTypeLimit
                 });
                 });
             }
             }
             if (item is MusicAlbum)
             if (item is MusicAlbum)
@@ -350,7 +351,8 @@ namespace MediaBrowser.Api.Library
                     Id = request.Id,
                     Id = request.Id,
                     Limit = request.Limit,
                     Limit = request.Limit,
                     UserId = request.UserId,
                     UserId = request.UserId,
-                    ExcludeArtistIds = request.ExcludeArtistIds
+                    ExcludeArtistIds = request.ExcludeArtistIds,
+                    ImageTypeLimit = request.ImageTypeLimit
                 });
                 });
             }
             }
             if (item is MusicArtist)
             if (item is MusicArtist)
@@ -364,7 +366,8 @@ namespace MediaBrowser.Api.Library
                     Fields = request.Fields,
                     Fields = request.Fields,
                     Id = request.Id,
                     Id = request.Id,
                     Limit = request.Limit,
                     Limit = request.Limit,
-                    UserId = request.UserId
+                    UserId = request.UserId,
+                    ImageTypeLimit = request.ImageTypeLimit
                 });
                 });
             }
             }
 
 
@@ -381,7 +384,8 @@ namespace MediaBrowser.Api.Library
                     Fields = request.Fields,
                     Fields = request.Fields,
                     Id = request.Id,
                     Id = request.Id,
                     Limit = request.Limit,
                     Limit = request.Limit,
-                    UserId = request.UserId
+                    UserId = request.UserId,
+                    ImageTypeLimit = request.ImageTypeLimit
                 });
                 });
             }
             }
 
 
@@ -396,7 +400,8 @@ namespace MediaBrowser.Api.Library
                     Fields = request.Fields,
                     Fields = request.Fields,
                     Id = request.Id,
                     Id = request.Id,
                     Limit = request.Limit,
                     Limit = request.Limit,
-                    UserId = request.UserId
+                    UserId = request.UserId,
+                    ImageTypeLimit = request.ImageTypeLimit
                 });
                 });
             }
             }
 
 

+ 2 - 31
MediaBrowser.Api/Session/SessionsService.cs

@@ -66,7 +66,7 @@ namespace MediaBrowser.Api.Session
 
 
     [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")]
     [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")]
     [Authenticated]
     [Authenticated]
-    public class Play : IReturnVoid
+    public class Play : PlayRequest
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the id.
         /// Gets or sets the id.
@@ -74,27 +74,6 @@ namespace MediaBrowser.Api.Session
         /// <value>The id.</value>
         /// <value>The id.</value>
         [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
         public string Id { get; set; }
         public string Id { get; set; }
-
-        /// <summary>
-        /// Artist, Genre, Studio, Person, or any kind of BaseItem
-        /// </summary>
-        /// <value>The type of the item.</value>
-        [ApiMember(Name = "ItemIds", Description = "The ids of the items to play, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
-        public string ItemIds { get; set; }
-
-        /// <summary>
-        /// Gets or sets the start position ticks that the first item should be played at
-        /// </summary>
-        /// <value>The start position ticks.</value>
-        [ApiMember(Name = "StartPositionTicks", Description = "The starting position of the first item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public long? StartPositionTicks { get; set; }
-
-        /// <summary>
-        /// Gets or sets the play command.
-        /// </summary>
-        /// <value>The play command.</value>
-        [ApiMember(Name = "PlayCommand", Description = "The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public PlayCommand PlayCommand { get; set; }
     }
     }
 
 
     [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")]
     [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")]
@@ -471,15 +450,7 @@ namespace MediaBrowser.Api.Session
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         public void Post(Play request)
         public void Post(Play request)
         {
         {
-            var command = new PlayRequest
-            {
-                ItemIds = request.ItemIds.Split(','),
-
-                PlayCommand = request.PlayCommand,
-                StartPositionTicks = request.StartPositionTicks
-            };
-
-            var task = _sessionManager.SendPlayCommand(GetSession(_sessionContext).Result.Id, request.Id, command, CancellationToken.None);
+            var task = _sessionManager.SendPlayCommand(GetSession(_sessionContext).Result.Id, request.Id, request, CancellationToken.None);
 
 
             Task.WaitAll(task);
             Task.WaitAll(task);
         }
         }

+ 34 - 1
MediaBrowser.Api/StartupWizardService.cs

@@ -9,6 +9,8 @@ using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.Services;
+using MediaBrowser.Common.Net;
+using System.Threading;
 
 
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
 {
 {
@@ -50,14 +52,16 @@ namespace MediaBrowser.Api
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
         private readonly IConnectManager _connectManager;
         private readonly IConnectManager _connectManager;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IMediaEncoder _mediaEncoder;
+        private readonly IHttpClient _httpClient;
 
 
-        public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, IMediaEncoder mediaEncoder)
+        public StartupWizardService(IServerConfigurationManager config, IHttpClient httpClient, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, IMediaEncoder mediaEncoder)
         {
         {
             _config = config;
             _config = config;
             _appHost = appHost;
             _appHost = appHost;
             _userManager = userManager;
             _userManager = userManager;
             _connectManager = connectManager;
             _connectManager = connectManager;
             _mediaEncoder = mediaEncoder;
             _mediaEncoder = mediaEncoder;
+            _httpClient = httpClient;
         }
         }
 
 
         public void Post(ReportStartupWizardComplete request)
         public void Post(ReportStartupWizardComplete request)
@@ -65,6 +69,35 @@ namespace MediaBrowser.Api
             _config.Configuration.IsStartupWizardCompleted = true;
             _config.Configuration.IsStartupWizardCompleted = true;
             _config.SetOptimalValues();
             _config.SetOptimalValues();
             _config.SaveConfiguration();
             _config.SaveConfiguration();
+
+            Task.Run(UpdateStats);
+        }
+
+        private async Task UpdateStats()
+        {
+            try
+            {
+                var url = string.Format("http://www.mb3admin.com/admin/service/package/installed?mac={0}&product=MBServer&operation=Install&version={1}",
+                    _appHost.SystemId,
+                    _appHost.ApplicationVersion.ToString());
+
+                using (var response = await _httpClient.SendAsync(new HttpRequestOptions
+                {
+
+                    Url = url,
+                    CancellationToken = CancellationToken.None,
+                    LogErrors = false,
+                    LogRequest = false
+
+                }, "GET").ConfigureAwait(false))
+                {
+
+                }
+            }
+            catch
+            {
+
+            }
         }
         }
 
 
         public object Get(GetStartupInfo request)
         public object Get(GetStartupInfo request)

+ 4 - 2
MediaBrowser.Api/VideosService.cs

@@ -114,12 +114,14 @@ namespace MediaBrowser.Api
 
 
             foreach (var link in video.GetLinkedAlternateVersions())
             foreach (var link in video.GetLinkedAlternateVersions())
             {
             {
-                link.PrimaryVersionId = null;
+                link.SetPrimaryVersionId(null);
+                link.LinkedAlternateVersions = Video.EmptyLinkedChildArray;
 
 
                 link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
                 link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
             }
             }
 
 
             video.LinkedAlternateVersions = Video.EmptyLinkedChildArray;
             video.LinkedAlternateVersions = Video.EmptyLinkedChildArray;
+            video.SetPrimaryVersionId(null);
             video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
             video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
         }
         }
 
 
@@ -174,7 +176,7 @@ namespace MediaBrowser.Api
 
 
             foreach (var item in items.Where(i => i.Id != primaryVersion.Id))
             foreach (var item in items.Where(i => i.Id != primaryVersion.Id))
             {
             {
-                item.PrimaryVersionId = primaryVersion.Id.ToString("N");
+                item.SetPrimaryVersionId(primaryVersion.Id.ToString("N"));
 
 
                 item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
                 item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
 
 

+ 2 - 0
MediaBrowser.Common/Net/HttpRequestOptions.cs

@@ -93,6 +93,8 @@ namespace MediaBrowser.Common.Net
         public bool LogRequest { get; set; }
         public bool LogRequest { get; set; }
         public bool LogRequestAsDebug { get; set; }
         public bool LogRequestAsDebug { get; set; }
         public bool LogErrors { get; set; }
         public bool LogErrors { get; set; }
+        public bool LogResponse { get; set; }
+        public bool LogResponseHeaders { get; set; }
 
 
         public bool LogErrorResponseBody { get; set; }
         public bool LogErrorResponseBody { get; set; }
         public bool EnableKeepAlive { get; set; }
         public bool EnableKeepAlive { get; set; }

+ 1 - 6
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -1376,11 +1376,6 @@ namespace MediaBrowser.Controller.Entities
             return list;
             return list;
         }
         }
 
 
-        internal virtual bool IsValidFromResolver(BaseItem newItem)
-        {
-            return true;
-        }
-
         internal virtual ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
         internal virtual ItemUpdateType UpdateFromResolvedItem(BaseItem newItem)
         {
         {
             var updateType = ItemUpdateType.None;
             var updateType = ItemUpdateType.None;
@@ -2045,7 +2040,7 @@ namespace MediaBrowser.Controller.Entities
                 .Where(i => i.IsLocalFile)
                 .Where(i => i.IsLocalFile)
                 .Select(i => FileSystem.GetDirectoryName(i.Path))
                 .Select(i => FileSystem.GetDirectoryName(i.Path))
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
-                .SelectMany(i => FileSystem.GetFilePaths(i))
+                .SelectMany(i => directoryService.GetFilePaths(i))
                 .ToList();
                 .ToList();
 
 
             var deletedImages = ImageInfos
             var deletedImages = ImageInfos

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

@@ -265,21 +265,6 @@ namespace MediaBrowser.Controller.Entities
             return changed;
             return changed;
         }
         }
 
 
-        internal override bool IsValidFromResolver(BaseItem newItem)
-        {
-            var newCollectionFolder = newItem as CollectionFolder;
-
-            if (newCollectionFolder != null)
-            {
-                if (!string.Equals(CollectionType, newCollectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-            }
-
-            return base.IsValidFromResolver(newItem);
-        }
-
         private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
         private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
         {
         {
             var path = ContainingFolderPath;
             var path = ContainingFolderPath;

+ 11 - 1
MediaBrowser.Controller/Entities/Folder.cs

@@ -416,7 +416,7 @@ namespace MediaBrowser.Controller.Entities
                 {
                 {
                     BaseItem currentChild;
                     BaseItem currentChild;
 
 
-                    if (currentChildren.TryGetValue(child.Id, out currentChild) && currentChild.IsValidFromResolver(child))
+                    if (currentChildren.TryGetValue(child.Id, out currentChild))
                     {
                     {
                         validChildren.Add(currentChild);
                         validChildren.Add(currentChild);
 
 
@@ -1421,6 +1421,16 @@ namespace MediaBrowser.Controller.Entities
             // Sweep through recursively and update status
             // Sweep through recursively and update status
             foreach (var item in itemsResult)
             foreach (var item in itemsResult)
             {
             {
+                if (item.IsVirtualItem)
+                {
+                    // The querying doesn't support virtual unaired
+                    var episode = item as Episode;
+                    if (episode != null && episode.IsUnaired)
+                    {
+                        continue;
+                    }
+                }
+
                 item.MarkPlayed(user, datePlayed, resetPosition);
                 item.MarkPlayed(user, datePlayed, resetPosition);
             }
             }
         }
         }

+ 27 - 51
MediaBrowser.Controller/Entities/Video.cs

@@ -84,6 +84,20 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
+        public void SetPrimaryVersionId(string id)
+        {
+            if (string.IsNullOrWhiteSpace(id))
+            {
+                PrimaryVersionId = null;
+            }
+            else
+            {
+                PrimaryVersionId = id;
+            }
+
+            PresentationUniqueKey = CreatePresentationUniqueKey();
+        }
+
         public override string CreatePresentationUniqueKey()
         public override string CreatePresentationUniqueKey()
         {
         {
             if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
             if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
@@ -667,8 +681,6 @@ namespace MediaBrowser.Controller.Entities
                 throw new ArgumentNullException("media");
                 throw new ArgumentNullException("media");
             }
             }
 
 
-            var mediaStreams = MediaSourceManager.GetMediaStreams(media.Id);
-
             var locationType = media.LocationType;
             var locationType = media.LocationType;
 
 
             var info = new MediaSourceInfo
             var info = new MediaSourceInfo
@@ -676,8 +688,8 @@ namespace MediaBrowser.Controller.Entities
                 Id = media.Id.ToString("N"),
                 Id = media.Id.ToString("N"),
                 IsoType = media.IsoType,
                 IsoType = media.IsoType,
                 Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File,
                 Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File,
-                MediaStreams = mediaStreams,
-                Name = GetMediaSourceName(media, mediaStreams),
+                MediaStreams = MediaSourceManager.GetMediaStreams(media.Id),
+                Name = GetMediaSourceName(media),
                 Path = enablePathSubstitution ? GetMappedPath(media, media.Path, locationType) : media.Path,
                 Path = enablePathSubstitution ? GetMappedPath(media, media.Path, locationType) : media.Path,
                 RunTimeTicks = media.RunTimeTicks,
                 RunTimeTicks = media.RunTimeTicks,
                 Video3DFormat = media.Video3DFormat,
                 Video3DFormat = media.Video3DFormat,
@@ -740,12 +752,20 @@ namespace MediaBrowser.Controller.Entities
             return info;
             return info;
         }
         }
 
 
-        private static string GetMediaSourceName(Video video, List<MediaStream> mediaStreams)
+        private static string GetMediaSourceName(Video video)
         {
         {
             var terms = new List<string>();
             var terms = new List<string>();
 
 
-            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
-            var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
+            var locationType = video.LocationType;
+            var path = video.Path;
+            if ((locationType == LocationType.FileSystem || locationType == LocationType.Offline) && !string.IsNullOrWhiteSpace(path))
+            {
+                terms.Add(System.IO.Path.GetFileName(path));
+            }
+            else
+            {
+                terms.Add(video.Name);
+            }
 
 
             if (video.Video3DFormat.HasValue)
             if (video.Video3DFormat.HasValue)
             {
             {
@@ -779,50 +799,6 @@ namespace MediaBrowser.Controller.Entities
                 }
                 }
             }
             }
 
 
-            if (videoStream != null)
-            {
-                if (videoStream.Width.HasValue)
-                {
-                    if (videoStream.Width.Value >= 3800)
-                    {
-                        terms.Add("4K");
-                    }
-                    else if (videoStream.Width.Value >= 1900)
-                    {
-                        terms.Add("1080P");
-                    }
-                    else if (videoStream.Width.Value >= 1270)
-                    {
-                        terms.Add("720P");
-                    }
-                    else if (videoStream.Width.Value >= 700)
-                    {
-                        terms.Add("480P");
-                    }
-                    else
-                    {
-                        terms.Add("SD");
-                    }
-                }
-            }
-
-            if (videoStream != null && !string.IsNullOrWhiteSpace(videoStream.Codec))
-            {
-                terms.Add(videoStream.Codec.ToUpper());
-            }
-
-            if (audioStream != null)
-            {
-                var audioCodec = string.Equals(audioStream.Codec, "dca", StringComparison.OrdinalIgnoreCase)
-                    ? audioStream.Profile
-                    : audioStream.Codec;
-
-                if (!string.IsNullOrEmpty(audioCodec))
-                {
-                    terms.Add(audioCodec.ToUpper());
-                }
-            }
-
             return string.Join("/", terms.ToArray(terms.Count));
             return string.Join("/", terms.ToArray(terms.Count));
         }
         }
 
 

+ 23 - 0
MediaBrowser.Controller/IO/StreamHelper.cs

@@ -1,6 +1,7 @@
 using System.IO;
 using System.IO;
 using System.Threading;
 using System.Threading;
 using System;
 using System;
+using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Controller.IO
 namespace MediaBrowser.Controller.IO
 {
 {
@@ -23,5 +24,27 @@ namespace MediaBrowser.Controller.IO
                 }
                 }
             }
             }
         }
         }
+
+        public static async Task CopyToAsync(Stream source, Stream destination, int bufferSize, IProgress<double> progress, long contentLength, CancellationToken cancellationToken)
+        {
+            byte[] buffer = new byte[bufferSize];
+            int read;
+            long totalRead = 0;
+
+            while ((read = source.Read(buffer, 0, buffer.Length)) != 0)
+            {
+                cancellationToken.ThrowIfCancellationRequested();
+
+                destination.Write(buffer, 0, read);
+
+                totalRead += read;
+
+                double pct = totalRead;
+                pct /= contentLength;
+                pct *= 100;
+
+                progress.Report(pct);
+            }
+        }
     }
     }
 }
 }

+ 1 - 1
MediaBrowser.Controller/LiveTv/ITunerHost.cs

@@ -63,8 +63,8 @@ namespace MediaBrowser.Controller.LiveTv
         void Close();
         void Close();
         int ConsumerCount { get; }
         int ConsumerCount { get; }
         string OriginalStreamId { get; set; }
         string OriginalStreamId { get; set; }
+        string TunerHostId { get; }
         bool EnableStreamSharing { get; set; }
         bool EnableStreamSharing { get; set; }
-        ITunerHost TunerHost { get; set; }
         MediaSourceInfo OpenedMediaSource { get; set; }
         MediaSourceInfo OpenedMediaSource { get; set; }
         string UniqueId { get; }
         string UniqueId { get; }
         List<string> SharedStreamIds { get; }
         List<string> SharedStreamIds { get; }

+ 9 - 7
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -1325,6 +1325,8 @@ namespace MediaBrowser.Controller.MediaEncoding
             if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
             if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
             {
             {
                 videoSizeParam = string.Format("scale={0}:{1}", state.VideoStream.Width.Value.ToString(_usCulture), state.VideoStream.Height.Value.ToString(_usCulture));
                 videoSizeParam = string.Format("scale={0}:{1}", state.VideoStream.Width.Value.ToString(_usCulture), state.VideoStream.Height.Value.ToString(_usCulture));
+
+                videoSizeParam += ":force_original_aspect_ratio=decrease";
             }
             }
 
 
             var mapPrefix = state.SubtitleStream.IsExternal ?
             var mapPrefix = state.SubtitleStream.IsExternal ?
@@ -1335,7 +1337,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 ? 0
                 ? 0
                 : state.SubtitleStream.Index;
                 : state.SubtitleStream.Index;
 
 
-            return string.Format(" -filter_complex \"[{0}:{1}]{4}[sub] ; [0:{2}] [sub] overlay{3}\"",
+            return string.Format(" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"",
                 mapPrefix.ToString(_usCulture),
                 mapPrefix.ToString(_usCulture),
                 subtitleStreamIndex.ToString(_usCulture),
                 subtitleStreamIndex.ToString(_usCulture),
                 state.VideoStream.Index.ToString(_usCulture),
                 state.VideoStream.Index.ToString(_usCulture),
@@ -2094,6 +2096,12 @@ namespace MediaBrowser.Controller.MediaEncoding
                     args += " -avoid_negative_ts disabled -start_at_zero";
                     args += " -avoid_negative_ts disabled -start_at_zero";
                 }
                 }
 
 
+                // This is for internal graphical subs
+                if (hasGraphicalSubs)
+                {
+                    args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
+                }
+
                 var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset);
                 var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset);
 
 
                 if (!string.IsNullOrEmpty(qualityParam))
                 if (!string.IsNullOrEmpty(qualityParam))
@@ -2101,12 +2109,6 @@ namespace MediaBrowser.Controller.MediaEncoding
                     args += " " + qualityParam.Trim();
                     args += " " + qualityParam.Trim();
                 }
                 }
 
 
-                // This is for internal graphical subs
-                if (hasGraphicalSubs)
-                {
-                    args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
-                }
-
                 if (!state.RunTimeTicks.HasValue)
                 if (!state.RunTimeTicks.HasValue)
                 {
                 {
                     args += " -flags -global_header";
                     args += " -flags -global_header";

+ 2 - 1
MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs

@@ -3,6 +3,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Controller.Providers;
 
 
 namespace MediaBrowser.Controller.MediaEncoding
 namespace MediaBrowser.Controller.MediaEncoding
 {
 {
@@ -11,6 +12,6 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// <summary>
         /// <summary>
         /// Refreshes the chapter images.
         /// Refreshes the chapter images.
         /// </summary>
         /// </summary>
-        Task<bool> RefreshChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
+        Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
     }
     }
 }
 }

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

@@ -118,9 +118,6 @@ namespace MediaBrowser.Controller.MediaEncoding
         void UpdateEncoderPath(string path, string pathType);
         void UpdateEncoderPath(string path, string pathType);
         bool SupportsEncoder(string encoder);
         bool SupportsEncoder(string encoder);
 
 
-        void SetLogFilename(string name);
-        void ClearLogFilename();
-
         string[] GetPlayableStreamFileNames(string path, VideoType videoType);
         string[] GetPlayableStreamFileNames(string path, VideoType videoType);
         IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber);
         IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber);
     }
     }

+ 28 - 24
MediaBrowser.Controller/Providers/DirectoryService.cs

@@ -14,11 +14,11 @@ namespace MediaBrowser.Controller.Providers
         private readonly ILogger _logger;
         private readonly ILogger _logger;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
 
 
-        private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache =
-            new ConcurrentDictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase);
+        private readonly Dictionary<string, FileSystemMetadata[]> _cache = new Dictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase);
 
 
-        private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache =
-        new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
+        private readonly Dictionary<string, FileSystemMetadata> _fileCache = new Dictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
+
+        private readonly Dictionary<string, List<string>> _filePathCache = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
 
 
         public DirectoryService(ILogger logger, IFileSystem fileSystem)
         public DirectoryService(ILogger logger, IFileSystem fileSystem)
         {
         {
@@ -32,11 +32,6 @@ namespace MediaBrowser.Controller.Providers
         }
         }
 
 
         public FileSystemMetadata[] GetFileSystemEntries(string path)
         public FileSystemMetadata[] GetFileSystemEntries(string path)
-        {
-            return GetFileSystemEntries(path, false);
-        }
-
-        private FileSystemMetadata[] GetFileSystemEntries(string path, bool clearCache)
         {
         {
             if (string.IsNullOrWhiteSpace(path))
             if (string.IsNullOrWhiteSpace(path))
             {
             {
@@ -45,13 +40,6 @@ namespace MediaBrowser.Controller.Providers
 
 
             FileSystemMetadata[] entries;
             FileSystemMetadata[] entries;
 
 
-            if (clearCache)
-            {
-                FileSystemMetadata[] removed;
-
-                _cache.TryRemove(path, out removed);
-            }
-
             if (!_cache.TryGetValue(path, out entries))
             if (!_cache.TryGetValue(path, out entries))
             {
             {
                 //_logger.Debug("Getting files for " + path);
                 //_logger.Debug("Getting files for " + path);
@@ -66,21 +54,17 @@ namespace MediaBrowser.Controller.Providers
                     entries = new FileSystemMetadata[] { };
                     entries = new FileSystemMetadata[] { };
                 }
                 }
 
 
-                _cache.TryAdd(path, entries);
+                //_cache.TryAdd(path, entries);
+                _cache[path] = entries;
             }
             }
 
 
             return entries;
             return entries;
         }
         }
 
 
         public List<FileSystemMetadata> GetFiles(string path)
         public List<FileSystemMetadata> GetFiles(string path)
-        {
-            return GetFiles(path, false);
-        }
-
-        public List<FileSystemMetadata> GetFiles(string path, bool clearCache)
         {
         {
             var list = new List<FileSystemMetadata>();
             var list = new List<FileSystemMetadata>();
-            var items = GetFileSystemEntries(path, clearCache);
+            var items = GetFileSystemEntries(path);
             foreach (var item in items)
             foreach (var item in items)
             {
             {
                 if (!item.IsDirectory)
                 if (!item.IsDirectory)
@@ -100,7 +84,8 @@ namespace MediaBrowser.Controller.Providers
 
 
                 if (file != null && file.Exists)
                 if (file != null && file.Exists)
                 {
                 {
-                    _fileCache.TryAdd(path, file);
+                    //_fileCache.TryAdd(path, file);
+                    _fileCache[path] = file;
                 }
                 }
                 else
                 else
                 {
                 {
@@ -111,5 +96,24 @@ namespace MediaBrowser.Controller.Providers
             return file;
             return file;
             //return _fileSystem.GetFileInfo(path);
             //return _fileSystem.GetFileInfo(path);
         }
         }
+
+        public List<string> GetFilePaths(string path)
+        {
+            return GetFilePaths(path, false);
+        }
+
+        public List<string> GetFilePaths(string path, bool clearCache)
+        {
+            List<string> result;
+            if (clearCache || !_filePathCache.TryGetValue(path, out result))
+            {
+                result = _fileSystem.GetFilePaths(path).ToList();
+
+                _filePathCache[path] = result;
+            }
+
+            return result;
+        }
+
     }
     }
 }
 }

+ 3 - 0
MediaBrowser.Controller/Providers/IDirectoryService.cs

@@ -8,5 +8,8 @@ namespace MediaBrowser.Controller.Providers
         FileSystemMetadata[] GetFileSystemEntries(string path);
         FileSystemMetadata[] GetFileSystemEntries(string path);
         List<FileSystemMetadata> GetFiles(string path);
         List<FileSystemMetadata> GetFiles(string path);
         FileSystemMetadata GetFile(string path);
         FileSystemMetadata GetFile(string path);
+
+        List<string> GetFilePaths(string path);
+        List<string> GetFilePaths(string path, bool clearCache);
     }
     }
 }
 }

+ 85 - 19
MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs

@@ -9,6 +9,7 @@ using System.Globalization;
 using System.Linq;
 using System.Linq;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
+using MediaBrowser.Controller.Entities.Movies;
 
 
 namespace MediaBrowser.LocalMetadata.Images
 namespace MediaBrowser.LocalMetadata.Images
 {
 {
@@ -131,35 +132,91 @@ namespace MediaBrowser.LocalMetadata.Images
 
 
             PopulatePrimaryImages(item, images, files, imagePrefix, isInMixedFolder);
             PopulatePrimaryImages(item, images, files, imagePrefix, isInMixedFolder);
 
 
-            AddImage(files, images, "logo", imagePrefix, isInMixedFolder, ImageType.Logo);
-            AddImage(files, images, "clearart", imagePrefix, isInMixedFolder, ImageType.Art);
+            var added = false;
+            var isEpisode = item is Episode;
+            var isSong = item.GetType() == typeof(Audio);
+            var isGame = item is Game;
+            var isPerson = item is Person;
+
+            // Logo
+            if (!isEpisode && !isSong && !isPerson)
+            {
+                added = AddImage(files, images, "logo", imagePrefix, isInMixedFolder, ImageType.Logo);
+                if (!added)
+                {
+                    added = AddImage(files, images, "clearlogo", imagePrefix, isInMixedFolder, ImageType.Logo);
+                }
+            }
+
+            // Art
+            if (!isEpisode && !isSong && !isPerson)
+            {
+                AddImage(files, images, "clearart", imagePrefix, isInMixedFolder, ImageType.Art);
+            }
 
 
             // For music albums, prefer cdart before disc
             // For music albums, prefer cdart before disc
             if (item is MusicAlbum)
             if (item is MusicAlbum)
             {
             {
-                AddImage(files, images, "cdart", imagePrefix, isInMixedFolder, ImageType.Disc);
-                AddImage(files, images, "disc", imagePrefix, isInMixedFolder, ImageType.Disc);
+                added = AddImage(files, images, "cdart", imagePrefix, isInMixedFolder, ImageType.Disc);
+
+                if (!added)
+                {
+                    added = AddImage(files, images, "disc", imagePrefix, isInMixedFolder, ImageType.Disc);
+                }
             }
             }
-            else
+            else if (isGame || item is Video || item is BoxSet)
             {
             {
-                AddImage(files, images, "disc", imagePrefix, isInMixedFolder, ImageType.Disc);
-                AddImage(files, images, "cdart", imagePrefix, isInMixedFolder, ImageType.Disc);
+                added = AddImage(files, images, "disc", imagePrefix, isInMixedFolder, ImageType.Disc);
+
+                if (!added)
+                {
+                    added = AddImage(files, images, "cdart", imagePrefix, isInMixedFolder, ImageType.Disc);
+                }
+
+                if (!added)
+                {
+                    added = AddImage(files, images, "discart", imagePrefix, isInMixedFolder, ImageType.Disc);
+                }
             }
             }
 
 
-            AddImage(files, images, "box", imagePrefix, isInMixedFolder, ImageType.Box);
-            AddImage(files, images, "back", imagePrefix, isInMixedFolder, ImageType.BoxRear);
-            AddImage(files, images, "boxrear", imagePrefix, isInMixedFolder, ImageType.BoxRear);
-            AddImage(files, images, "menu", imagePrefix, isInMixedFolder, ImageType.Menu);
+            if (isGame)
+            {
+                AddImage(files, images, "box", imagePrefix, isInMixedFolder, ImageType.Box);
+                AddImage(files, images, "menu", imagePrefix, isInMixedFolder, ImageType.Menu);
+
+                added = AddImage(files, images, "back", imagePrefix, isInMixedFolder, ImageType.BoxRear);
+
+                if (!added)
+                {
+                    added = AddImage(files, images, "boxrear", imagePrefix, isInMixedFolder, ImageType.BoxRear);
+                }
+            }
 
 
             // Banner
             // Banner
-            AddImage(files, images, "banner", imagePrefix, isInMixedFolder, ImageType.Banner);
+            if (!isEpisode && !isSong && !isPerson)
+            {
+                AddImage(files, images, "banner", imagePrefix, isInMixedFolder, ImageType.Banner);
+            }
 
 
             // Thumb
             // Thumb
-            AddImage(files, images, "landscape", imagePrefix, isInMixedFolder, ImageType.Thumb);
-            AddImage(files, images, "thumb", imagePrefix, isInMixedFolder, ImageType.Thumb);
+            if (!isEpisode && !isSong && !isPerson)
+            {
+                added = AddImage(files, images, "landscape", imagePrefix, isInMixedFolder, ImageType.Thumb);
+                if (!added)
+                {
+                    added = AddImage(files, images, "thumb", imagePrefix, isInMixedFolder, ImageType.Thumb);
+                }
+            }
+
+            if (!isEpisode && !isSong && !isPerson)
+            {
+                PopulateBackdrops(item, images, files, imagePrefix, isInMixedFolder, directoryService);
+            }
 
 
-            PopulateBackdrops(item, images, files, imagePrefix, isInMixedFolder, directoryService);
-            PopulateScreenshots(images, files, imagePrefix, isInMixedFolder);
+            if (item is IHasScreenshots)
+            {
+                PopulateScreenshots(images, files, imagePrefix, isInMixedFolder);
+            }
         }
         }
 
 
         private static readonly string[] CommonImageFileNames = new[]
         private static readonly string[] CommonImageFileNames = new[]
@@ -232,19 +289,28 @@ namespace MediaBrowser.LocalMetadata.Images
             var fileNameWithoutExtension = item.FileNameWithoutExtension;
             var fileNameWithoutExtension = item.FileNameWithoutExtension;
             if (!string.IsNullOrEmpty(fileNameWithoutExtension))
             if (!string.IsNullOrEmpty(fileNameWithoutExtension))
             {
             {
-                AddImage(files, images, fileNameWithoutExtension, ImageType.Primary);
+                if (AddImage(files, images, fileNameWithoutExtension, ImageType.Primary))
+                {
+                    return;
+                }
             }
             }
 
 
             foreach (var name in imageFileNames)
             foreach (var name in imageFileNames)
             {
             {
-                AddImage(files, images, imagePrefix + name, ImageType.Primary);
+                if (AddImage(files, images, imagePrefix + name, ImageType.Primary))
+                {
+                    return;
+                }
             }
             }
 
 
             if (!isInMixedFolder)
             if (!isInMixedFolder)
             {
             {
                 foreach (var name in imageFileNames)
                 foreach (var name in imageFileNames)
                 {
                 {
-                    AddImage(files, images, name, ImageType.Primary);
+                    if (AddImage(files, images, name, ImageType.Primary))
+                    {
+                        return;
+                    }
                 }
                 }
             }
             }
         }
         }

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

@@ -226,7 +226,19 @@ namespace MediaBrowser.LocalMetadata.Savers
 
 
             if (wasHidden || ConfigurationManager.Configuration.SaveMetadataHidden)
             if (wasHidden || ConfigurationManager.Configuration.SaveMetadataHidden)
             {
             {
-                FileSystem.SetHidden(path, true);
+                SetHidden(path, true);
+            }
+        }
+
+        private void SetHidden(string path, bool hidden)
+        {
+            try
+            {
+                FileSystem.SetHidden(path, hidden);
+            }
+            catch (Exception ex)
+            {
+                Logger.Error("Error setting hidden attribute on {0} - {1}", path, ex.Message);
             }
             }
         }
         }
 
 

+ 0 - 1
MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs

@@ -18,7 +18,6 @@ namespace MediaBrowser.Model.Configuration
 
 
             SaveImagePathsInNfo = true;
             SaveImagePathsInNfo = true;
             EnablePathSubstitution = true;
             EnablePathSubstitution = true;
-            EnableExtraThumbsDuplication = true;
         }
         }
     }
     }
 }
 }

+ 6 - 12
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -1335,21 +1335,15 @@ namespace MediaBrowser.Model.Dlna
                 return true;
                 return true;
             }
             }
 
 
-            if (!maxBitrate.HasValue)
-            {
-                _logger.Info("Cannot " + playMethod + " due to unknown supported bitrate");
-                return false;
-            }
+            var requestedMaxBitrate = maxBitrate ?? 1000000;
 
 
-            if (!item.Bitrate.HasValue)
-            {
-                _logger.Info("Cannot " + playMethod + " due to unknown content bitrate");
-                return false;
-            }
+            // If we don't know the bitrate, then force a transcode if requested max bitrate is under 40 mbps
+            var itemBitrate = item.Bitrate ?? 
+                40000000;
 
 
-            if (item.Bitrate.Value > maxBitrate.Value)
+            if (itemBitrate > requestedMaxBitrate)
             {
             {
-                _logger.Info("Bitrate exceeds " + playMethod + " limit: media bitrate: {0}, max bitrate: {1}", item.Bitrate.Value.ToString(CultureInfo.InvariantCulture), maxBitrate.Value.ToString(CultureInfo.InvariantCulture));
+                _logger.Info("Bitrate exceeds " + playMethod + " limit: media bitrate: {0}, max bitrate: {1}", item.Bitrate.Value.ToString(CultureInfo.InvariantCulture), requestedMaxBitrate.ToString(CultureInfo.InvariantCulture));
                 return false;
                 return false;
             }
             }
 
 

+ 1 - 1
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -75,7 +75,7 @@ namespace MediaBrowser.Model.Dto
         public bool? CanDownload { get; set; }
         public bool? CanDownload { get; set; }
 
 
         public bool? HasSubtitles { get; set; }
         public bool? HasSubtitles { get; set; }
-        
+
         public string PreferredMetadataLanguage { get; set; }
         public string PreferredMetadataLanguage { get; set; }
         public string PreferredMetadataCountryCode { get; set; }
         public string PreferredMetadataCountryCode { get; set; }
 
 

+ 77 - 5
MediaBrowser.Model/Entities/MediaStream.cs

@@ -46,13 +46,13 @@ namespace MediaBrowser.Model.Entities
         {
         {
             get
             get
             {
             {
-                if (!string.IsNullOrEmpty(Title))
-                {
-                    return AddLanguageIfNeeded(Title);
-                }
-
                 if (Type == MediaStreamType.Audio)
                 if (Type == MediaStreamType.Audio)
                 {
                 {
+                    //if (!string.IsNullOrEmpty(Title))
+                    //{
+                    //    return AddLanguageIfNeeded(Title);
+                    //}
+
                     List<string> attributes = new List<string>();
                     List<string> attributes = new List<string>();
 
 
                     if (!string.IsNullOrEmpty(Language))
                     if (!string.IsNullOrEmpty(Language))
@@ -84,8 +84,32 @@ namespace MediaBrowser.Model.Entities
                     return string.Join(" ", attributes.ToArray(attributes.Count));
                     return string.Join(" ", attributes.ToArray(attributes.Count));
                 }
                 }
 
 
+                if (Type == MediaStreamType.Video)
+                {
+                    List<string> attributes = new List<string>();
+
+                    var resolutionText = GetResolutionText();
+
+                    if (!string.IsNullOrEmpty(resolutionText))
+                    {
+                        attributes.Add(resolutionText);
+                    }
+
+                    if (!string.IsNullOrEmpty(Codec))
+                    {
+                        attributes.Add(Codec.ToUpper());
+                    }
+
+                    return string.Join(" ", attributes.ToArray(attributes.Count));
+                }
+
                 if (Type == MediaStreamType.Subtitle)
                 if (Type == MediaStreamType.Subtitle)
                 {
                 {
+                    //if (!string.IsNullOrEmpty(Title))
+                    //{
+                    //    return AddLanguageIfNeeded(Title);
+                    //}
+
                     List<string> attributes = new List<string>();
                     List<string> attributes = new List<string>();
 
 
                     if (!string.IsNullOrEmpty(Language))
                     if (!string.IsNullOrEmpty(Language))
@@ -121,6 +145,54 @@ namespace MediaBrowser.Model.Entities
             }
             }
         }
         }
 
 
+        private string GetResolutionText()
+        {
+            var i = this;
+
+            if (i.Width.HasValue)
+            {
+                if (i.Width >= 3800)
+                {
+                    return "4K";
+                }
+                if (i.Width >= 2500)
+                {
+                    if (i.IsInterlaced)
+                    {
+                        return "1440I";
+                    }
+                    return "1440P";
+                }
+                if (i.Width >= 1900)
+                {
+                    if (i.IsInterlaced)
+                    {
+                        return "1080I";
+                    }
+                    return "1080P";
+                }
+                if (i.Width >= 1260)
+                {
+                    if (i.IsInterlaced)
+                    {
+                        return "720I";
+                    }
+                    return "720P";
+                }
+                if (i.Width >= 700)
+                {
+
+                    if (i.IsInterlaced)
+                    {
+                        return "480I";
+                    }
+                    return "480P";
+                }
+
+            }
+            return null;
+        }
+
         private string AddLanguageIfNeeded(string title)
         private string AddLanguageIfNeeded(string title)
         {
         {
             if (!string.IsNullOrEmpty(Language) && 
             if (!string.IsNullOrEmpty(Language) && 

+ 1 - 0
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

@@ -47,6 +47,7 @@ namespace MediaBrowser.Model.LiveTv
         public bool EnableStreamLooping { get; set; }
         public bool EnableStreamLooping { get; set; }
         public bool EnableNewHdhrChannelIds { get; set; }
         public bool EnableNewHdhrChannelIds { get; set; }
         public string Source { get; set; }
         public string Source { get; set; }
+        public int TunerCount { get; set; }
 
 
         public TunerHostInfo()
         public TunerHostInfo()
         {
         {

+ 2 - 1
MediaBrowser.Model/Session/GeneralCommandType.cs

@@ -39,6 +39,7 @@
         ChannelDown = 31,
         ChannelDown = 31,
         SetMaxStreamingBitrate = 31,
         SetMaxStreamingBitrate = 31,
         Guide = 32,
         Guide = 32,
-        ToggleStats = 33
+        ToggleStats = 33,
+        PlayMediaSource = 34
     }
     }
 }
 }

+ 9 - 1
MediaBrowser.Model/Session/PlayRequest.cs

@@ -1,4 +1,5 @@
-
+using MediaBrowser.Model.Services;
+
 namespace MediaBrowser.Model.Session
 namespace MediaBrowser.Model.Session
 {
 {
     /// <summary>
     /// <summary>
@@ -10,18 +11,21 @@ namespace MediaBrowser.Model.Session
         /// Gets or sets the item ids.
         /// Gets or sets the item ids.
         /// </summary>
         /// </summary>
         /// <value>The item ids.</value>
         /// <value>The item ids.</value>
+        [ApiMember(Name = "ItemIds", Description = "The ids of the items to play, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
         public string[] ItemIds { get; set; }
         public string[] ItemIds { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the start position ticks that the first item should be played at
         /// Gets or sets the start position ticks that the first item should be played at
         /// </summary>
         /// </summary>
         /// <value>The start position ticks.</value>
         /// <value>The start position ticks.</value>
+        [ApiMember(Name = "StartPositionTicks", Description = "The starting position of the first item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public long? StartPositionTicks { get; set; }
         public long? StartPositionTicks { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the play command.
         /// Gets or sets the play command.
         /// </summary>
         /// </summary>
         /// <value>The play command.</value>
         /// <value>The play command.</value>
+        [ApiMember(Name = "PlayCommand", Description = "The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
         public PlayCommand PlayCommand { get; set; }
         public PlayCommand PlayCommand { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -29,5 +33,9 @@ namespace MediaBrowser.Model.Session
         /// </summary>
         /// </summary>
         /// <value>The controlling user identifier.</value>
         /// <value>The controlling user identifier.</value>
         public string ControllingUserId { get; set; }
         public string ControllingUserId { get; set; }
+
+        public int? SubtitleStreamIndex { get; set; }
+        public int? AudioStreamIndex { get; set; }
+        public string MediaSourceId { get; set; }
     }
     }
 }
 }

+ 13 - 1
MediaBrowser.Providers/Manager/ImageSaver.cs

@@ -256,7 +256,7 @@ namespace MediaBrowser.Providers.Manager
 
 
                 if (_config.Configuration.SaveMetadataHidden)
                 if (_config.Configuration.SaveMetadataHidden)
                 {
                 {
-                    _fileSystem.SetHidden(path, true);
+                    SetHidden(path, true);
                 }
                 }
             }
             }
             finally
             finally
@@ -266,6 +266,18 @@ namespace MediaBrowser.Providers.Manager
             }
             }
         }
         }
 
 
+        private void SetHidden(string path, bool hidden)
+        {
+            try
+            {
+                _fileSystem.SetHidden(path, hidden);
+            }
+            catch (Exception ex)
+            {
+                _logger.Error("Error setting hidden attribute on {0} - {1}", path, ex.Message);
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the save paths.
         /// Gets the save paths.
         /// </summary>
         /// </summary>

+ 5 - 3
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -27,6 +27,7 @@ namespace MediaBrowser.Providers.Manager
         protected readonly IFileSystem FileSystem;
         protected readonly IFileSystem FileSystem;
         protected readonly IUserDataManager UserDataManager;
         protected readonly IUserDataManager UserDataManager;
         protected readonly ILibraryManager LibraryManager;
         protected readonly ILibraryManager LibraryManager;
+        private readonly SubtitleResolver _subtitleResolver;
 
 
         protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager)
         protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager)
         {
         {
@@ -36,6 +37,8 @@ namespace MediaBrowser.Providers.Manager
             FileSystem = fileSystem;
             FileSystem = fileSystem;
             UserDataManager = userDataManager;
             UserDataManager = userDataManager;
             LibraryManager = libraryManager;
             LibraryManager = libraryManager;
+
+            _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager, fileSystem);
         }
         }
 
 
         public async Task<ItemUpdateType> RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
         public async Task<ItemUpdateType> RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
@@ -76,8 +79,7 @@ namespace MediaBrowser.Providers.Manager
                         if (video != null && !video.IsPlaceHolder)
                         if (video != null && !video.IsPlaceHolder)
                         {
                         {
                             requiresRefresh = !video.SubtitleFiles
                             requiresRefresh = !video.SubtitleFiles
-                                .SequenceEqual(SubtitleResolver.GetSubtitleFiles(video, refreshOptions.DirectoryService, FileSystem, false)
-                                .OrderBy(i => i), StringComparer.OrdinalIgnoreCase);
+                                .SequenceEqual(_subtitleResolver.GetExternalSubtitleFiles(video, refreshOptions.DirectoryService, false), StringComparer.Ordinal);
                         }
                         }
                     }
                     }
                 }
                 }
@@ -124,7 +126,7 @@ namespace MediaBrowser.Providers.Manager
                 var providers = GetProviders(item, refreshOptions, isFirstRefresh, requiresRefresh)
                 var providers = GetProviders(item, refreshOptions, isFirstRefresh, requiresRefresh)
                     .ToList();
                     .ToList();
 
 
-                if (providers.Count > 0 || isFirstRefresh)
+                if (providers.Count > 0 || isFirstRefresh || requiresRefresh)
                 {
                 {
                     if (item.BeforeMetadataRefresh())
                     if (item.BeforeMetadataRefresh())
                     {
                     {

+ 3 - 3
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -229,7 +229,7 @@ namespace MediaBrowser.Providers.MediaInfo
                     extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
                     extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
                 }
                 }
 
 
-                await _encodingManager.RefreshChapterImages(video, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false);
+                await _encodingManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false);
 
 
                 _chapterManager.SaveChapters(video.Id.ToString(), chapters);
                 _chapterManager.SaveChapters(video.Id.ToString(), chapters);
             }
             }
@@ -472,7 +472,7 @@ namespace MediaBrowser.Providers.MediaInfo
             var subtitleResolver = new SubtitleResolver(_localization, _fileSystem);
             var subtitleResolver = new SubtitleResolver(_localization, _fileSystem);
 
 
             var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
             var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
-            var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, false).ToList();
+            var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, false);
 
 
             var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
             var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
                                             options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
                                             options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
@@ -497,7 +497,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 // Rescan
                 // Rescan
                 if (downloadedLanguages.Count > 0)
                 if (downloadedLanguages.Count > 0)
                 {
                 {
-                    externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, true).ToList();
+                    externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, true);
                 }
                 }
             }
             }
 
 

部分文件因为文件数量过多而无法显示