Просмотр исходного кода

Merge pull request #2767 from MediaBrowser/beta

Beta
Luke 8 лет назад
Родитель
Сommit
5ec9d4e9fe
69 измененных файлов с 1006 добавлено и 439 удалено
  1. 11 2
      Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs
  2. 354 2
      Emby.Dlna/ContentDirectory/ControlHandler.cs
  3. 40 0
      Emby.Dlna/Didl/DidlBuilder.cs
  4. 40 20
      Emby.Dlna/Eventing/EventManager.cs
  5. 3 2
      Emby.Dlna/PlayTo/PlaylistItemFactory.cs
  6. 1 8
      Emby.Dlna/Profiles/LgTvProfile.cs
  7. 1 2
      Emby.Dlna/Profiles/Xml/LG Smart TV.xml
  8. 4 4
      Emby.Dlna/Service/BaseService.cs
  9. 1 1
      Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
  10. 2 2
      Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
  11. 4 1
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  12. 1 1
      Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs
  13. 27 1
      Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
  14. 9 0
      Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
  15. 1 1
      Emby.Server.Implementations/Library/MediaSourceManager.cs
  16. 2 2
      Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs
  17. 34 13
      Emby.Server.Implementations/LiveTv/LiveTvManager.cs
  18. 2 2
      Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
  19. 33 1
      Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
  20. 4 7
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
  21. 7 8
      Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
  22. 5 0
      Emby.Server.Implementations/Localization/Ratings/es.txt
  23. 5 4
      Emby.Server.Implementations/Localization/Ratings/nz.txt
  24. 2 23
      Emby.Server.Implementations/ScheduledTasks/SystemUpdateTask.cs
  25. 0 2
      Emby.Server.Implementations/Updates/InstallationManager.cs
  26. 1 1
      Emby.Server.Implementations/packages.config
  27. 7 26
      MediaBrowser.Api/Dlna/DlnaServerService.cs
  28. 57 1
      MediaBrowser.Api/EnvironmentService.cs
  29. 4 1
      MediaBrowser.Api/Images/ImageService.cs
  30. 0 4
      MediaBrowser.Api/Library/LibraryService.cs
  31. 11 9
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  32. 9 13
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  33. 22 17
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  34. 10 9
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  35. 4 4
      MediaBrowser.Api/Playback/MediaInfoService.cs
  36. 2 3
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  37. 2 3
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  38. 1 1
      MediaBrowser.Api/Playback/StreamState.cs
  39. 1 1
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  40. 15 1
      MediaBrowser.Common/Extensions/ResourceNotFoundException.cs
  41. 1 1
      MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
  42. 2 9
      MediaBrowser.Controller/Dlna/IEventManager.cs
  43. 16 0
      MediaBrowser.Controller/Entities/Video.cs
  44. 1 4
      MediaBrowser.Controller/Library/IMediaSourceProvider.cs
  45. 5 1
      MediaBrowser.Controller/Providers/DirectoryService.cs
  46. 14 0
      MediaBrowser.Controller/Session/SessionInfo.cs
  47. 2 0
      MediaBrowser.Controller/Sync/ISyncManager.cs
  48. 3 1
      MediaBrowser.Controller/Sync/ISyncProvider.cs
  49. 1 1
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  50. 16 2
      MediaBrowser.Model/Dlna/DirectPlayProfile.cs
  51. 60 74
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  52. 2 2
      MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
  53. 2 2
      MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
  54. 2 1
      MediaBrowser.Model/Session/TranscodingInfo.cs
  55. 4 1
      MediaBrowser.Model/Sync/SyncJob.cs
  56. 9 12
      MediaBrowser.Providers/Music/ArtistMetadataService.cs
  57. 30 2
      MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
  58. 0 2
      MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
  59. 29 89
      MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
  60. 2 2
      MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
  61. 2 2
      MediaBrowser.Server.Mono/packages.config
  62. 34 10
      MediaBrowser.ServerApplication/MainStartup.cs
  63. 2 6
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  64. 2 3
      MediaBrowser.ServerApplication/packages.config
  65. 1 4
      MediaBrowser.WebDashboard/Api/PackageCreator.cs
  66. 17 2
      MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
  67. 5 0
      MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
  68. 1 1
      Nuget/MediaBrowser.Common.nuspec
  69. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 11 - 2
Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -736,10 +736,10 @@ namespace Emby.Common.Implementations.HttpClientManager
             {
                 if (options.LogErrors)
                 {
-                    _logger.ErrorException("Error getting response from " + options.Url, ex);
+                    _logger.ErrorException("Error " + webException.Status + " getting response from " + options.Url, webException);
                 }
 
-                var exception = new HttpException(ex.Message, ex);
+                var exception = new HttpException(webException.Message, webException);
 
                 var response = webException.Response as HttpWebResponse;
                 if (response != null)
@@ -752,6 +752,15 @@ namespace Emby.Common.Implementations.HttpClientManager
                     }
                 }
 
+                if (!exception.StatusCode.HasValue)
+                {
+                    if (webException.Status == WebExceptionStatus.NameResolutionFailure ||
+                        webException.Status == WebExceptionStatus.ConnectFailure)
+                    {
+                        exception.IsTimedOut = true;
+                    }
+                }
+
                 return exception;
             }
 

+ 354 - 2
Emby.Dlna/ContentDirectory/ControlHandler.cs

@@ -26,6 +26,7 @@ using System.Xml;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Xml;
 
@@ -482,6 +483,12 @@ namespace Emby.Dlna.ContentDirectory
                 return GetMusicArtistItems(item, null, user, 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 (stubType.HasValue)
             {
                 if (stubType.Value == StubType.People)
@@ -518,7 +525,7 @@ namespace Emby.Dlna.ContentDirectory
                 StartIndex = startIndex,
                 User = user,
                 IsMissing = false,
-                PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Music },
+                PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows },
                 ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
                 IsPlaceHolder = false,
                 DtoOptions = GetDtoOptions()
@@ -531,6 +538,278 @@ namespace Emby.Dlna.ContentDirectory
             return ToResult(queryResult);
         }
 
+        private QueryResult<ServerItem> GetMusicFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
+        {
+            var query = new InternalItemsQuery(user)
+            {
+                StartIndex = startIndex,
+                Limit = limit
+            };
+            SetSorting(query, sort, false);
+
+            if (stubType.HasValue && stubType.Value == StubType.Latest)
+            {
+                return GetMusicLatest(item, user, query);
+            }
+
+            if (stubType.HasValue && stubType.Value == StubType.Playlists)
+            {
+                return GetMusicPlaylists(item, user, query);
+            }
+
+            if (stubType.HasValue && stubType.Value == StubType.Albums)
+            {
+                return GetMusicAlbums(item, user, query);
+            }
+
+            if (stubType.HasValue && stubType.Value == StubType.Artists)
+            {
+                return GetMusicArtists(item, user, query);
+            }
+
+            if (stubType.HasValue && stubType.Value == StubType.AlbumArtists)
+            {
+                return GetMusicAlbumArtists(item, user, query);
+            }
+
+            if (stubType.HasValue && stubType.Value == StubType.FavoriteAlbums)
+            {
+                return GetFavoriteAlbums(item, user, query);
+            }
+
+            if (stubType.HasValue && stubType.Value == StubType.FavoriteArtists)
+            {
+                return GetFavoriteArtists(item, user, query);
+            }
+
+            if (stubType.HasValue && stubType.Value == StubType.FavoriteSongs)
+            {
+                return GetFavoriteSongs(item, user, query);
+            }
+
+            if (stubType.HasValue && stubType.Value == StubType.Songs)
+            {
+                return GetMusicSongs(item, user, query);
+            }
+
+            if (stubType.HasValue && stubType.Value == StubType.Genres)
+            {
+                return GetMusicGenres(item, user, query);
+            }
+
+            var list = new List<ServerItem>();
+
+            list.Add(new ServerItem(item)
+            {
+                StubType = StubType.Latest
+            });
+
+            list.Add(new ServerItem(item)
+            {
+                StubType = StubType.Playlists
+            });
+
+            list.Add(new ServerItem(item)
+            {
+                StubType = StubType.Albums
+            });
+
+            list.Add(new ServerItem(item)
+            {
+                StubType = StubType.AlbumArtists
+            });
+
+            list.Add(new ServerItem(item)
+            {
+                StubType = StubType.Artists
+            });
+
+            list.Add(new ServerItem(item)
+            {
+                StubType = StubType.Songs
+            });
+
+            list.Add(new ServerItem(item)
+            {
+                StubType = StubType.Genres
+            });
+
+            list.Add(new ServerItem(item)
+            {
+                StubType = StubType.FavoriteArtists
+            });
+
+            list.Add(new ServerItem(item)
+            {
+                StubType = StubType.FavoriteAlbums
+            });
+
+            list.Add(new ServerItem(item)
+            {
+                StubType = StubType.FavoriteSongs
+            });
+
+            return new QueryResult<ServerItem>
+            {
+                Items = list.ToArray(),
+                TotalRecordCount = list.Count
+            };
+        }
+
+        private QueryResult<ServerItem> GetMusicAlbums(BaseItem parent, User user, InternalItemsQuery query)
+        {
+            query.Recursive = true;
+            query.Parent = parent;
+            query.SetUser(user);
+
+            query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
+
+            var result = _libraryManager.GetItemsResult(query);
+
+            return ToResult(result);
+        }
+
+        private QueryResult<ServerItem> GetMusicSongs(BaseItem parent, User user, InternalItemsQuery query)
+        {
+            query.Recursive = true;
+            query.Parent = parent;
+            query.SetUser(user);
+
+            query.IncludeItemTypes = new[] { typeof(Audio).Name };
+
+            var result = _libraryManager.GetItemsResult(query);
+
+            return ToResult(result);
+        }
+
+        private QueryResult<ServerItem> GetFavoriteSongs(BaseItem parent, User user, InternalItemsQuery query)
+        {
+            query.Recursive = true;
+            query.Parent = parent;
+            query.SetUser(user);
+            query.IsFavorite = true;
+            query.IncludeItemTypes = new[] { typeof(Audio).Name };
+
+            var result = _libraryManager.GetItemsResult(query);
+
+            return ToResult(result);
+        }
+
+        private QueryResult<ServerItem> GetFavoriteAlbums(BaseItem parent, User user, InternalItemsQuery query)
+        {
+            query.Recursive = true;
+            query.Parent = parent;
+            query.SetUser(user);
+            query.IsFavorite = true;
+            query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
+
+            var result = _libraryManager.GetItemsResult(query);
+
+            return ToResult(result);
+        }
+
+        private QueryResult<ServerItem> GetMusicGenres(BaseItem parent, User user, InternalItemsQuery query)
+        {
+            var genresResult = _libraryManager.GetMusicGenres(new InternalItemsQuery(user)
+            {
+                AncestorIds = new[] { parent.Id.ToString("N") },
+                StartIndex = query.StartIndex,
+                Limit = query.Limit
+            });
+
+            var result = new QueryResult<BaseItem>
+            {
+                TotalRecordCount = genresResult.TotalRecordCount,
+                Items = genresResult.Items.Select(i => i.Item1).ToArray()
+            };
+
+            return ToResult(result);
+        }
+
+        private QueryResult<ServerItem> GetMusicAlbumArtists(BaseItem parent, User user, InternalItemsQuery query)
+        {
+            var artists = _libraryManager.GetAlbumArtists(new InternalItemsQuery(user)
+            {
+                AncestorIds = new[] { parent.Id.ToString("N") },
+                StartIndex = query.StartIndex,
+                Limit = query.Limit
+            });
+
+            var result = new QueryResult<BaseItem>
+            {
+                TotalRecordCount = artists.TotalRecordCount,
+                Items = artists.Items.Select(i => i.Item1).ToArray()
+            };
+
+            return ToResult(result);
+        }
+
+        private QueryResult<ServerItem> GetMusicArtists(BaseItem parent, User user, InternalItemsQuery query)
+        {
+            var artists = _libraryManager.GetArtists(new InternalItemsQuery(user)
+            {
+                AncestorIds = new[] { parent.Id.ToString("N") },
+                StartIndex = query.StartIndex,
+                Limit = query.Limit
+            });
+
+            var result = new QueryResult<BaseItem>
+            {
+                TotalRecordCount = artists.TotalRecordCount,
+                Items = artists.Items.Select(i => i.Item1).ToArray()
+            };
+
+            return ToResult(result);
+        }
+
+        private QueryResult<ServerItem> GetFavoriteArtists(BaseItem parent, User user, InternalItemsQuery query)
+        {
+            var artists = _libraryManager.GetArtists(new InternalItemsQuery(user)
+            {
+                AncestorIds = new[] { parent.Id.ToString("N") },
+                StartIndex = query.StartIndex,
+                Limit = query.Limit,
+                IsFavorite = true
+            });
+
+            var result = new QueryResult<BaseItem>
+            {
+                TotalRecordCount = artists.TotalRecordCount,
+                Items = artists.Items.Select(i => i.Item1).ToArray()
+            };
+
+            return ToResult(result);
+        }
+
+        private QueryResult<ServerItem> GetMusicPlaylists(BaseItem parent, User user, InternalItemsQuery query)
+        {
+            query.Parent = null;
+            query.IncludeItemTypes = new[] { typeof(Playlist).Name };
+            query.SetUser(user);
+            query.Recursive = true;
+
+            var result = _libraryManager.GetItemsResult(query);
+
+            return ToResult(result);
+        }
+
+        private QueryResult<ServerItem> GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query)
+        {
+            query.SortBy = new string[] { };
+
+            var items = _userViewManager.GetLatestItems(new LatestItemsQuery
+            {
+                UserId = user.Id.ToString("N"),
+                Limit = 50,
+                IncludeItemTypes = new[] { typeof(Audio).Name },
+                ParentId = parent == null ? null : parent.Id.ToString("N"),
+                GroupItems = true
+
+            }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToList();
+
+            return ToResult(items);
+        }
+
         private QueryResult<ServerItem> GetMusicArtistItems(BaseItem item, Guid? parentId, User user, SortCriteria sort, int? startIndex, int? limit)
         {
             var query = new InternalItemsQuery(user)
@@ -571,6 +850,19 @@ namespace Emby.Dlna.ContentDirectory
             return ToResult(result);
         }
 
+        private QueryResult<ServerItem> ToResult(List<BaseItem> result)
+        {
+            var serverItems = result
+                .Select(i => new ServerItem(i))
+                .ToArray();
+
+            return new QueryResult<ServerItem>
+            {
+                TotalRecordCount = result.Count,
+                Items = serverItems
+            };
+        }
+
         private QueryResult<ServerItem> ToResult(QueryResult<BaseItem> result)
         {
             var serverItems = result
@@ -660,6 +952,56 @@ namespace Emby.Dlna.ContentDirectory
                 stubType = StubType.People;
                 id = id.Split(new[] { '_' }, 2)[1];
             }
+            else if (id.StartsWith("latest_", StringComparison.OrdinalIgnoreCase))
+            {
+                stubType = StubType.Latest;
+                id = id.Split(new[] { '_' }, 2)[1];
+            }
+            else if (id.StartsWith("playlists_", StringComparison.OrdinalIgnoreCase))
+            {
+                stubType = StubType.Playlists;
+                id = id.Split(new[] { '_' }, 2)[1];
+            }
+            else if (id.StartsWith("Albums_", StringComparison.OrdinalIgnoreCase))
+            {
+                stubType = StubType.Albums;
+                id = id.Split(new[] { '_' }, 2)[1];
+            }
+            else if (id.StartsWith("AlbumArtists_", StringComparison.OrdinalIgnoreCase))
+            {
+                stubType = StubType.AlbumArtists;
+                id = id.Split(new[] { '_' }, 2)[1];
+            }
+            else if (id.StartsWith("Artists_", StringComparison.OrdinalIgnoreCase))
+            {
+                stubType = StubType.Artists;
+                id = id.Split(new[] { '_' }, 2)[1];
+            }
+            else if (id.StartsWith("Genres_", StringComparison.OrdinalIgnoreCase))
+            {
+                stubType = StubType.Genres;
+                id = id.Split(new[] { '_' }, 2)[1];
+            }
+            else if (id.StartsWith("Songs_", StringComparison.OrdinalIgnoreCase))
+            {
+                stubType = StubType.Songs;
+                id = id.Split(new[] { '_' }, 2)[1];
+            }
+            else if (id.StartsWith("FavoriteAlbums_", StringComparison.OrdinalIgnoreCase))
+            {
+                stubType = StubType.FavoriteAlbums;
+                id = id.Split(new[] { '_' }, 2)[1];
+            }
+            else if (id.StartsWith("FavoriteArtists_", StringComparison.OrdinalIgnoreCase))
+            {
+                stubType = StubType.FavoriteArtists;
+                id = id.Split(new[] { '_' }, 2)[1];
+            }
+            else if (id.StartsWith("FavoriteSongs_", StringComparison.OrdinalIgnoreCase))
+            {
+                stubType = StubType.FavoriteSongs;
+                id = id.Split(new[] { '_' }, 2)[1];
+            }
 
             if (Guid.TryParse(id, out itemId))
             {
@@ -696,6 +1038,16 @@ namespace Emby.Dlna.ContentDirectory
     public enum StubType
     {
         Folder = 0,
-        People = 1
+        People = 1,
+        Latest = 2,
+        Playlists = 3,
+        Albums = 4,
+        AlbumArtists = 5,
+        Artists = 6,
+        Songs = 7,
+        Genres = 8,
+        FavoriteSongs = 9,
+        FavoriteArtists = 10,
+        FavoriteAlbums = 11
     }
 }

+ 40 - 0
Emby.Dlna/Didl/DidlBuilder.cs

@@ -399,6 +399,46 @@ namespace Emby.Dlna.Didl
                 }
                 return _localization.GetLocalizedString("HeaderPeople");
             }
+            if (itemStubType.HasValue && itemStubType.Value == StubType.Latest)
+            {
+                return _localization.GetLocalizedString("ViewTypeMusicLatest");
+            }
+            if (itemStubType.HasValue && itemStubType.Value == StubType.Playlists)
+            {
+                return _localization.GetLocalizedString("ViewTypeMusicPlaylists");
+            }
+            if (itemStubType.HasValue && itemStubType.Value == StubType.AlbumArtists)
+            {
+                return _localization.GetLocalizedString("ViewTypeMusicAlbumArtists");
+            }
+            if (itemStubType.HasValue && itemStubType.Value == StubType.Albums)
+            {
+                return _localization.GetLocalizedString("ViewTypeMusicAlbums");
+            }
+            if (itemStubType.HasValue && itemStubType.Value == StubType.Artists)
+            {
+                return _localization.GetLocalizedString("ViewTypeMusicArtists");
+            }
+            if (itemStubType.HasValue && itemStubType.Value == StubType.Songs)
+            {
+                return _localization.GetLocalizedString("ViewTypeMusicSongs");
+            }
+            if (itemStubType.HasValue && itemStubType.Value == StubType.Genres)
+            {
+                return _localization.GetLocalizedString("ViewTypeTvGenres");
+            }
+            if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteAlbums)
+            {
+                return _localization.GetLocalizedString("ViewTypeMusicFavoriteAlbums");
+            }
+            if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteArtists)
+            {
+                return _localization.GetLocalizedString("ViewTypeMusicFavoriteArtists");
+            }
+            if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteSongs)
+            {
+                return _localization.GetLocalizedString("ViewTypeMusicFavoriteSongs");
+            }
 
             var episode = item as Episode;
             var season = context as Season;

+ 40 - 20
Emby.Dlna/Eventing/EventManager.cs

@@ -26,32 +26,34 @@ namespace Emby.Dlna.Eventing
             _logger = logger;
         }
 
-        public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, int? timeoutSeconds)
+        public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string requestedTimeoutString)
         {
-            var timeout = timeoutSeconds ?? 300;
-
             var subscription = GetSubscription(subscriptionId, true);
 
-            _logger.Debug("Renewing event subscription for {0} with timeout of {1} to {2}",
-                subscription.NotificationType,
-                timeout,
-                subscription.CallbackUrl);
+            // Remove logging for now because some devices are sending this very frequently
+            // TODO re-enable with dlna debug logging setting
+            //_logger.Debug("Renewing event subscription for {0} with timeout of {1} to {2}",
+            //    subscription.NotificationType,
+            //    timeout,
+            //    subscription.CallbackUrl);
 
-            subscription.TimeoutSeconds = timeout;
+            subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
             subscription.SubscriptionTime = DateTime.UtcNow;
 
-            return GetEventSubscriptionResponse(subscriptionId, timeout);
+            return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, subscription.TimeoutSeconds);
         }
 
-        public EventSubscriptionResponse CreateEventSubscription(string notificationType, int? timeoutSeconds, string callbackUrl)
+        public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
         {
-            var timeout = timeoutSeconds ?? 300;
+            var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
             var id = "uuid:" + Guid.NewGuid().ToString("N");
 
-            _logger.Debug("Creating event subscription for {0} with timeout of {1} to {2}",
-                notificationType,
-                timeout,
-                callbackUrl);
+            // Remove logging for now because some devices are sending this very frequently
+            // TODO re-enable with dlna debug logging setting
+            //_logger.Debug("Creating event subscription for {0} with timeout of {1} to {2}",
+            //    notificationType,
+            //    timeout,
+            //    callbackUrl);
 
             _subscriptions.TryAdd(id, new EventSubscription
             {
@@ -61,7 +63,25 @@ namespace Emby.Dlna.Eventing
                 TimeoutSeconds = timeout
             });
 
-            return GetEventSubscriptionResponse(id, timeout);
+            return GetEventSubscriptionResponse(id, requestedTimeoutString, timeout);
+        }
+
+        private int? ParseTimeout(string header)
+        {
+            if (!string.IsNullOrEmpty(header))
+            {
+                // Starts with SECOND-
+                header = header.Split('-').Last();
+
+                int val;
+
+                if (int.TryParse(header, NumberStyles.Any, _usCulture, out val))
+                {
+                    return val;
+                }
+            }
+
+            return null;
         }
 
         public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
@@ -73,22 +93,22 @@ namespace Emby.Dlna.Eventing
 
             return new EventSubscriptionResponse
             {
-                Content = "\r\n",
+                Content = string.Empty,
                 ContentType = "text/plain"
             };
         }
 
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-        private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, int timeoutSeconds)
+        private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
         {
             var response = new EventSubscriptionResponse
             {
-                Content = "\r\n",
+                Content = string.Empty,
                 ContentType = "text/plain"
             };
 
             response.Headers["SID"] = subscriptionId;
-            response.Headers["TIMEOUT"] = "SECOND-" + timeoutSeconds.ToString(_usCulture);
+            response.Headers["TIMEOUT"] = string.IsNullOrWhiteSpace(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString;
 
             return response;
         }

+ 3 - 2
Emby.Dlna/PlayTo/PlaylistItemFactory.cs

@@ -56,8 +56,9 @@ namespace Emby.Dlna.PlayTo
             if (profile.Container.Length > 0)
             {
                 // Check container type
-                var mediaContainer = Path.GetExtension(mediaPath);
-                if (!profile.GetContainers().Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase)))
+                var mediaContainer = (Path.GetExtension(mediaPath) ?? string.Empty).TrimStart('.');
+
+                if (!profile.SupportsContainer(mediaContainer))
                 {
                     return false;
                 }

+ 1 - 8
Emby.Dlna/Profiles/LgTvProfile.cs

@@ -53,14 +53,7 @@ namespace Emby.Dlna.Profiles
             {
                 new DirectPlayProfile
                 {
-                    Container = "ts",
-                    VideoCodec = "h264",
-                    AudioCodec = "aac,ac3,mp3,dca,dts",
-                    Type = DlnaProfileType.Video
-                },
-                new DirectPlayProfile
-                {
-                    Container = "mkv",
+                    Container = "ts,mpegts,avi,mkv",
                     VideoCodec = "h264",
                     AudioCodec = "aac,ac3,mp3,dca,dts",
                     Type = DlnaProfileType.Video

+ 1 - 2
Emby.Dlna/Profiles/Xml/LG Smart TV.xml

@@ -35,8 +35,7 @@
   <IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
   <XmlRootAttributes />
   <DirectPlayProfiles>
-    <DirectPlayProfile container="ts" audioCodec="aac,ac3,mp3,dca,dts" videoCodec="h264" type="Video" />
-    <DirectPlayProfile container="mkv" audioCodec="aac,ac3,mp3,dca,dts" videoCodec="h264" type="Video" />
+    <DirectPlayProfile container="ts,mpegts,avi,mkv" audioCodec="aac,ac3,mp3,dca,dts" videoCodec="h264" type="Video" />
     <DirectPlayProfile container="mp4,m4v" audioCodec="aac,ac3,mp3,dca,dts" videoCodec="h264,mpeg4" type="Video" />
     <DirectPlayProfile container="mp3" type="Audio" />
     <DirectPlayProfile container="jpeg" type="Photo" />

+ 4 - 4
Emby.Dlna/Service/BaseService.cs

@@ -24,14 +24,14 @@ namespace Emby.Dlna.Service
             return EventManager.CancelEventSubscription(subscriptionId);
         }
 
-        public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, int? timeoutSeconds)
+        public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string timeoutString)
         {
-            return EventManager.RenewEventSubscription(subscriptionId, timeoutSeconds);
+            return EventManager.RenewEventSubscription(subscriptionId, timeoutString);
         }
 
-        public EventSubscriptionResponse CreateEventSubscription(string notificationType, int? timeoutSeconds, string callbackUrl)
+        public EventSubscriptionResponse CreateEventSubscription(string notificationType, string timeoutString, string callbackUrl)
         {
-            return EventManager.CreateEventSubscription(notificationType, timeoutSeconds, callbackUrl);
+            return EventManager.CreateEventSubscription(notificationType, timeoutString, callbackUrl);
         }
     }
 }

+ 1 - 1
Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs

@@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Channels
             return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
         }
 
-        public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken)
+        public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, bool allowLiveStreamProbe, CancellationToken cancellationToken)
         {
             throw new NotImplementedException();
         }

+ 2 - 2
Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs

@@ -17,7 +17,7 @@ using MediaBrowser.Model.Tasks;
 
 namespace Emby.Server.Implementations.Data
 {
-    public class CleanDatabaseScheduledTask : IScheduledTask
+    public class CleanDatabaseScheduledTask : ILibraryPostScanTask
     {
         private readonly ILibraryManager _libraryManager;
         private readonly IItemRepository _itemRepo;
@@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Data
             get { return "Library"; }
         }
 
-        public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
+        public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
             // Ensure these objects are lazy loaded.
             // Without this there is a deadlock that will need to be investigated

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

@@ -307,7 +307,7 @@
   </ItemGroup>
   <ItemGroup>
     <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
-      <HintPath>..\packages\SQLitePCLRaw.core.1.1.6\lib\net45\SQLitePCLRaw.core.dll</HintPath>
+      <HintPath>..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="System" />
@@ -418,6 +418,9 @@
   <ItemGroup>
     <EmbeddedResource Include="Localization\Ratings\uk.txt" />
   </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Localization\Ratings\es.txt" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 1 - 1
Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs

@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.FFMpeg
             }
             if (!string.IsNullOrWhiteSpace(customffProbePath))
             {
-                info.EncoderPath = customffProbePath;
+                info.ProbePath = customffProbePath;
             }
 
             return info;

+ 27 - 1
Emby.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -104,6 +104,7 @@ namespace Emby.Server.Implementations.HttpServer
         readonly Dictionary<Type, int> _mapExceptionToStatusCode = new Dictionary<Type, int>
             {
                 {typeof (ResourceNotFoundException), 404},
+                {typeof (RemoteServiceUnavailableException), 502},
                 {typeof (FileNotFoundException), 404},
                 //{typeof (DirectoryNotFoundException), 404},
                 {typeof (SecurityException), 401},
@@ -268,6 +269,29 @@ namespace Emby.Server.Implementations.HttpServer
             }
         }
 
+        private Exception GetActualException(Exception ex)
+        {
+            var agg = ex as AggregateException;
+            if (agg != null)
+            {
+                var inner = agg.InnerException;
+                if (inner != null)
+                {
+                    return GetActualException(inner);
+                }
+                else
+                {
+                    var inners = agg.InnerExceptions;
+                    if (inners != null && inners.Count > 0)
+                    {
+                        return GetActualException(inners[0]);
+                    }
+                }
+            }
+
+            return ex;
+        }
+
         private int GetStatusCode(Exception ex)
         {
             if (ex is ArgumentException)
@@ -280,7 +304,7 @@ namespace Emby.Server.Implementations.HttpServer
             int statusCode;
             if (!_mapExceptionToStatusCode.TryGetValue(exceptionType, out statusCode))
             {
-                if (string.Equals(exceptionType.Name, "DirectoryNotFoundException", StringComparison.OrdinalIgnoreCase))
+                if (ex is DirectoryNotFoundException)
                 {
                     statusCode = 404;
                 }
@@ -297,6 +321,8 @@ namespace Emby.Server.Implementations.HttpServer
         {
             try
             {
+                ex = GetActualException(ex);
+
                 if (logException)
                 {
                     _logger.ErrorException("Error processing request", ex);

+ 9 - 0
Emby.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -438,6 +438,15 @@ namespace Emby.Server.Implementations.HttpServer
             options.CacheKey = cacheKey.GetMD5();
             options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare));
 
+            options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+            // Quotes are valid in linux. They'll possibly cause issues here
+            var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty);
+            if (!string.IsNullOrWhiteSpace(filename))
+            {
+                options.ResponseHeaders["Content-Disposition"] = "inline; filename=\"" + filename + "\"";
+            }
+
             return GetStaticResult(requestContext, options);
         }
 

+ 1 - 1
Emby.Server.Implementations/Library/MediaSourceManager.cs

@@ -371,7 +371,7 @@ namespace Emby.Server.Implementations.Library
                 var tuple = GetProvider(request.OpenToken);
                 var provider = tuple.Item1;
 
-                var mediaSourceTuple = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
+                var mediaSourceTuple = await provider.OpenMediaSource(tuple.Item2, request.EnableMediaProbe, cancellationToken).ConfigureAwait(false);
 
                 var mediaSource = mediaSourceTuple.Item1;
 

+ 2 - 2
Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs

@@ -16,8 +16,8 @@ namespace Emby.Server.Implementations.LiveTv
         private readonly IMediaEncoder _mediaEncoder;
         private readonly ILogger _logger;
 
-        const int ProbeAnalyzeDurationMs = 2000;
-        const int PlaybackAnalyzeDurationMs = 2000;
+        const int ProbeAnalyzeDurationMs = 3000;
+        const int PlaybackAnalyzeDurationMs = 3000;
 
         public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger)
         {

+ 34 - 13
Emby.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -1514,7 +1514,7 @@ namespace Emby.Server.Implementations.LiveTv
         }
 
         private DateTime _lastRecordingRefreshTime;
-        private async Task RefreshRecordings(CancellationToken cancellationToken)
+        private async Task RefreshRecordings(Guid internalLiveTvFolderId, CancellationToken cancellationToken)
         {
             const int cacheMinutes = 2;
 
@@ -1542,10 +1542,8 @@ namespace Emby.Server.Implementations.LiveTv
                 });
 
                 var results = await Task.WhenAll(tasks).ConfigureAwait(false);
-                var folder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
-                var parentFolderId = folder.Id;
 
-                var recordingTasks = results.SelectMany(i => i.ToList()).Select(i => CreateRecordingRecord(i.Item1, i.Item2.Name, parentFolderId, cancellationToken));
+                var recordingTasks = results.SelectMany(i => i.ToList()).Select(i => CreateRecordingRecord(i.Item1, i.Item2.Name, internalLiveTvFolderId, cancellationToken));
 
                 var idList = await Task.WhenAll(recordingTasks).ConfigureAwait(false);
 
@@ -1559,7 +1557,7 @@ namespace Emby.Server.Implementations.LiveTv
             }
         }
 
-        private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, DtoOptions dtoOptions, User user)
+        private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, DtoOptions dtoOptions, Guid internalLiveTvFolderId, User user)
         {
             if (user == null)
             {
@@ -1571,21 +1569,31 @@ namespace Emby.Server.Implementations.LiveTv
                 return new QueryResult<BaseItem>();
             }
 
-            var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders()
+            var folderIds = EmbyTV.EmbyTV.Current.GetRecordingFolders()
                 .SelectMany(i => i.Locations)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .Select(i => _libraryManager.FindByPath(i, true))
                 .Where(i => i != null)
                 .Where(i => i.IsVisibleStandalone(user))
+                .Select(i => i.Id)
                 .ToList();
 
-            if (folders.Count == 0)
+            var excludeItemTypes = new List<string>();
+
+            if (!query.IsInProgress.HasValue)
+            {
+                folderIds.Add(internalLiveTvFolderId);
+
+                excludeItemTypes.Add(typeof(LiveTvChannel).Name);
+                excludeItemTypes.Add(typeof(LiveTvProgram).Name);
+            }
+
+            if (folderIds.Count == 0)
             {
                 return new QueryResult<BaseItem>();
             }
 
             var includeItemTypes = new List<string>();
-            var excludeItemTypes = new List<string>();
             var genres = new List<string>();
 
             if (query.IsMovie.HasValue)
@@ -1631,7 +1639,7 @@ namespace Emby.Server.Implementations.LiveTv
             {
                 MediaTypes = new[] { MediaType.Video },
                 Recursive = true,
-                AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
+                AncestorIds = folderIds.Select(i => i.ToString("N")).ToArray(),
                 IsFolder = false,
                 IsVirtualItem = false,
                 Limit = query.Limit,
@@ -1714,12 +1722,24 @@ namespace Emby.Server.Implementations.LiveTv
                 return new QueryResult<BaseItem>();
             }
 
-            if (_services.Count == 1 && !(query.IsInProgress ?? false) && (!query.IsLibraryItem.HasValue || query.IsLibraryItem.Value))
+            var folder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
+
+            if (_services.Count == 1 && (!query.IsInProgress.HasValue || !query.IsInProgress.Value) && (!query.IsLibraryItem.HasValue || query.IsLibraryItem.Value))
             {
-                return GetEmbyRecordings(query, options, user);
+                if (!query.IsInProgress.HasValue)
+                {
+                    await RefreshRecordings(folder.Id, cancellationToken).ConfigureAwait(false);
+                }
+
+                return GetEmbyRecordings(query, options, folder.Id, user);
             }
 
-            await RefreshRecordings(cancellationToken).ConfigureAwait(false);
+            return await GetInternalRecordingsFromServices(query, user, options, folder.Id, cancellationToken).ConfigureAwait(false);
+        }
+
+        private async Task<QueryResult<BaseItem>> GetInternalRecordingsFromServices(RecordingQuery query, User user, DtoOptions options, Guid internalLiveTvFolderId, CancellationToken cancellationToken)
+        {
+            await RefreshRecordings(internalLiveTvFolderId, cancellationToken).ConfigureAwait(false);
 
             var internalQuery = new InternalItemsQuery(user)
             {
@@ -2620,7 +2640,8 @@ namespace Emby.Server.Implementations.LiveTv
 
             }, new DtoOptions(), cancellationToken).ConfigureAwait(false);
 
-            var recordings = recordingResult.Items.OfType<ILiveTvRecording>().ToList();
+            var embyServiceName = EmbyTV.EmbyTV.Current.Name;
+            var recordings = recordingResult.Items.Where(i => !string.Equals(i.ServiceName, embyServiceName, StringComparison.OrdinalIgnoreCase)).OfType<ILiveTvRecording>().ToList();
 
             var groups = new List<BaseItemDto>();
 

+ 2 - 2
Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs

@@ -118,7 +118,7 @@ namespace Emby.Server.Implementations.LiveTv
             return list;
         }
 
-        public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken)
+        public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, bool allowLiveStreamProbe, CancellationToken cancellationToken)
         {
             MediaSourceInfo stream = null;
             const bool isAudio = false;
@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.LiveTv
 
             try
             {
-                if (!stream.SupportsProbing || stream.MediaStreams.Any(i => i.Index != -1))
+                if (!allowLiveStreamProbe || !stream.SupportsProbing || stream.MediaStreams.Any(i => i.Index != -1))
                 {
                     AddMediaInfo(stream, isAudio, cancellationToken);
                 }

+ 33 - 1
Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs

@@ -10,9 +10,11 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 
 namespace Emby.Server.Implementations.LiveTv.TunerHosts
@@ -23,16 +25,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         protected readonly ILogger Logger;
         protected IJsonSerializer JsonSerializer;
         protected readonly IMediaEncoder MediaEncoder;
+        protected readonly IFileSystem FileSystem;
 
         private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
             new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
 
-        protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
+        protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem)
         {
             Config = config;
             Logger = logger;
             JsonSerializer = jsonSerializer;
             MediaEncoder = mediaEncoder;
+            FileSystem = fileSystem;
         }
 
         protected abstract Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken);
@@ -81,16 +85,44 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
             foreach (var host in hosts)
             {
+                var channelCacheFile = Path.Combine(Config.ApplicationPaths.CachePath, host.Id + "_channels");
+
                 try
                 {
                     var channels = await GetChannels(host, enableCache, cancellationToken).ConfigureAwait(false);
                     var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.OrdinalIgnoreCase))).ToList();
 
                     list.AddRange(newChannels);
+
+                    if (!enableCache)
+                    {
+                        try
+                        {
+                            FileSystem.CreateDirectory(FileSystem.GetDirectoryName(channelCacheFile));
+                            JsonSerializer.SerializeToFile(channels, channelCacheFile);
+                        }
+                        catch (IOException)
+                        {
+
+                        }
+                    }
                 }
                 catch (Exception ex)
                 {
                     Logger.ErrorException("Error getting channel list", ex);
+
+                    if (enableCache)
+                    {
+                        try
+                        {
+                            var channels = JsonSerializer.DeserializeFromFile<List<ChannelInfo>>(channelCacheFile);
+                            list.AddRange(channels);
+                        }
+                        catch (IOException)
+                        {
+
+                        }
+                    }
                 }
             }
 

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

@@ -27,17 +27,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
     public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
     {
         private readonly IHttpClient _httpClient;
-        private readonly IFileSystem _fileSystem;
         private readonly IServerApplicationHost _appHost;
         private readonly ISocketFactory _socketFactory;
         private readonly INetworkManager _networkManager;
         private readonly IEnvironmentInfo _environment;
 
-        public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient, IFileSystem fileSystem, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment)
-            : base(config, logger, jsonSerializer, mediaEncoder)
+        public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
         {
             _httpClient = httpClient;
-            _fileSystem = fileSystem;
             _appHost = appHost;
             _socketFactory = socketFactory;
             _networkManager = networkManager;
@@ -509,7 +506,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 
             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, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), 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
@@ -529,10 +526,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
                 }
                 mediaSource.Path = httpUrl;
 
-                return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment);
+                return new HdHomerunHttpStream(mediaSource, 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, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
         }
 
         public async Task Validate(TunerHostInfo info)

+ 7 - 8
Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs

@@ -25,15 +25,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 {
     public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
     {
-        private readonly IFileSystem _fileSystem;
         private readonly IHttpClient _httpClient;
         private readonly IServerApplicationHost _appHost;
         private readonly IEnvironmentInfo _environment;
 
-        public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment)
-            : base(config, logger, jsonSerializer, mediaEncoder)
+        public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
         {
-            _fileSystem = fileSystem;
             _httpClient = httpClient;
             _appHost = appHost;
             _environment = environment;
@@ -51,7 +48,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
         protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
         {
-            var result = await new M3uParser(Logger, _fileSystem, _httpClient, _appHost).Parse(info.Url, ChannelIdPrefix, info.Id, !info.EnableTvgId, cancellationToken).ConfigureAwait(false);
+            var result = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).Parse(info.Url, ChannelIdPrefix, info.Id, !info.EnableTvgId, cancellationToken).ConfigureAwait(false);
 
             return result.Cast<ChannelInfo>().ToList();
         }
@@ -76,13 +73,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
         {
             var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false);
 
-            var liveStream = new LiveStream(sources.First(), _environment, _fileSystem);
+            var liveStream = new LiveStream(sources.First(), _environment, FileSystem);
             return liveStream;
         }
 
         public async Task Validate(TunerHostInfo info)
         {
-            using (var stream = await new M3uParser(Logger, _fileSystem, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
+            using (var stream = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
             {
 
             }
@@ -154,7 +151,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 
                     Id = channel.Path.GetMD5().ToString("N"),
                     IsInfiniteStream = true,
-                    IsRemote = true
+                    IsRemote = true,
+
+                    IgnoreDts = true
                 };
 
                 mediaSource.InferTotalBitrate();

+ 5 - 0
Emby.Server.Implementations/Localization/Ratings/es.txt

@@ -0,0 +1,5 @@
+ES-A,1
+ES-7,3
+ES-12,6
+ES-16,8
+ES-18,11

+ 5 - 4
Emby.Server.Implementations/Localization/Ratings/nz.txt

@@ -1,10 +1,11 @@
 NZ-G,1
 NZ-PG,5
-NZ-M,9
+NZ-M,6
 NZ-R13,7
+NZ-RP13,7
 NZ-R15,8
+NZ-RP16,9
 NZ-R16,9
 NZ-R18,10
-NZ-RP13,7
-NZ-RP16,9
-NZ-R,10
+NZ-R,10
+NZ-MA,10

+ 2 - 23
Emby.Server.Implementations/ScheduledTasks/SystemUpdateTask.cs

@@ -68,23 +68,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// <returns>Task.</returns>
         public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
         {
-            EventHandler<double> innerProgressHandler = (sender, e) => progress.Report(e * .1);
-
             // Create a progress object for the update check
-            var innerProgress = new SimpleProgress<double>();
-            innerProgress.ProgressChanged += innerProgressHandler;
-
-            var updateInfo = await _appHost.CheckForApplicationUpdate(cancellationToken, innerProgress).ConfigureAwait(false);
-
-            // Release the event handler
-            innerProgress.ProgressChanged -= innerProgressHandler;
-
-            progress.Report(10);
+            var updateInfo = await _appHost.CheckForApplicationUpdate(cancellationToken, new SimpleProgress<double>()).ConfigureAwait(false);
 
             if (!updateInfo.IsUpdateAvailable)
             {
                 Logger.Debug("No application update available.");
-                progress.Report(100);
                 return;
             }
 
@@ -96,22 +85,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
             {
                 Logger.Info("Update Revision {0} available.  Updating...", updateInfo.AvailableVersion);
 
-                innerProgressHandler = (sender, e) => progress.Report(e * .9 + .1);
-
-                innerProgress = new SimpleProgress<double>();
-                innerProgress.ProgressChanged += innerProgressHandler;
-
-                await _appHost.UpdateApplication(updateInfo.Package, cancellationToken, innerProgress).ConfigureAwait(false);
-
-                // Release the event handler
-                innerProgress.ProgressChanged -= innerProgressHandler;
+                await _appHost.UpdateApplication(updateInfo.Package, cancellationToken, progress).ConfigureAwait(false);
             }
             else
             {
                 Logger.Info("A new version of " + _appHost.Name + " is available.");
             }
-
-            progress.Report(100);
         }
 
         /// <summary>

+ 0 - 2
Emby.Server.Implementations/Updates/InstallationManager.cs

@@ -513,8 +513,6 @@ namespace Emby.Server.Implementations.Updates
                     CurrentInstallations.Remove(tuple);
                 }
 
-                progress.Report(100);
-
                 CompletedInstallationsInternal.Add(installationInfo);
 
                 EventHelper.FireEventIfNotNull(PackageInstallationCompleted, this, installationEventArgs, _logger);

+ 1 - 1
Emby.Server.Implementations/packages.config

@@ -3,5 +3,5 @@
   <package id="Emby.XmlTv" version="1.0.9" targetFramework="net46" />
   <package id="MediaBrowser.Naming" version="1.0.5" targetFramework="portable45-net45+win8" />
   <package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" />
-  <package id="SQLitePCLRaw.core" version="1.1.6" targetFramework="net46" />
+  <package id="SQLitePCLRaw.core" version="1.1.7" targetFramework="net46" />
 </packages>

+ 7 - 26
MediaBrowser.Api/Dlna/DlnaServerService.cs

@@ -219,20 +219,20 @@ namespace MediaBrowser.Api.Dlna
         private object ProcessEventRequest(IEventManager eventManager)
         {
             var subscriptionId = GetHeader("SID");
-            var notificationType = GetHeader("NT");
-            var callback = GetHeader("CALLBACK");
-            var timeoutString = GetHeader("TIMEOUT");
-
-            var timeout = ParseTimeout(timeoutString);
 
             if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase))
             {
+                var notificationType = GetHeader("NT");
+
+                var callback = GetHeader("CALLBACK");
+                var timeoutString = GetHeader("TIMEOUT");
+
                 if (string.IsNullOrEmpty(notificationType))
                 {
-                    return GetSubscriptionResponse(eventManager.RenewEventSubscription(subscriptionId, timeout));
+                    return GetSubscriptionResponse(eventManager.RenewEventSubscription(subscriptionId, timeoutString));
                 }
 
-                return GetSubscriptionResponse(eventManager.CreateEventSubscription(notificationType, timeout, callback));
+                return GetSubscriptionResponse(eventManager.CreateEventSubscription(notificationType, timeoutString, callback));
             }
 
             return GetSubscriptionResponse(eventManager.CancelEventSubscription(subscriptionId));
@@ -242,24 +242,5 @@ namespace MediaBrowser.Api.Dlna
         {
             return ResultFactory.GetResult(response.Content, response.ContentType, response.Headers);
         }
-
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-        private int? ParseTimeout(string header)
-        {
-            if (!string.IsNullOrEmpty(header))
-            {
-                // Starts with SECOND-
-                header = header.Split('-').Last();
-
-                int val;
-
-                if (int.TryParse(header, NumberStyles.Any, _usCulture, out val))
-                {
-                    return val;
-                }
-            }
-
-            return null;
-        }
     }
 }

+ 57 - 1
MediaBrowser.Api/EnvironmentService.cs

@@ -50,6 +50,20 @@ namespace MediaBrowser.Api
         }
     }
 
+    [Route("/Environment/ValidatePath", "POST", Summary = "Gets the contents of a given directory in the file system")]
+    public class ValidatePath
+    {
+        /// <summary>
+        /// Gets or sets the path.
+        /// </summary>
+        /// <value>The path.</value>
+        [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string Path { get; set; }
+
+        public bool ValidateWriteable { get; set; }
+        public bool? IsFile { get; set; }
+    }
+
     [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")]
     public class GetNetworkShares : IReturn<List<FileSystemEntryInfo>>
     {
@@ -112,7 +126,7 @@ namespace MediaBrowser.Api
         /// The _network manager
         /// </summary>
         private readonly INetworkManager _networkManager;
-        private IFileSystem _fileSystem;
+        private readonly IFileSystem _fileSystem;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="EnvironmentService" /> class.
@@ -129,6 +143,48 @@ namespace MediaBrowser.Api
             _fileSystem = fileSystem;
         }
 
+        public void Post(ValidatePath request)
+        {
+            if (request.IsFile.HasValue)
+            {
+                if (request.IsFile.Value)
+                {
+                    if (!_fileSystem.FileExists(request.Path))
+                    {
+                        throw new FileNotFoundException("File not found", request.Path);
+                    }
+                }
+                else
+                {
+                    if (!_fileSystem.DirectoryExists(request.Path))
+                    {
+                        throw new FileNotFoundException("File not found", request.Path);
+                    }
+                }
+            }
+
+            else
+            {
+                if (!_fileSystem.FileExists(request.Path) && !_fileSystem.DirectoryExists(request.Path))
+                {
+                    throw new FileNotFoundException("Path not found", request.Path);
+                }
+
+                if (request.ValidateWriteable)
+                {
+                    EnsureWriteAccess(request.Path);
+                }
+            }
+        }
+
+        protected void EnsureWriteAccess(string path)
+        {
+            var file = Path.Combine(path, Guid.NewGuid().ToString());
+
+            _fileSystem.WriteAllText(file, string.Empty);
+            _fileSystem.DeleteFile(file);
+        }
+
         public object Get(GetDefaultDirectoryBrowser request)
         {
             var result = new DefaultDirectoryBrowserInfo();

+ 4 - 1
MediaBrowser.Api/Images/ImageService.cs

@@ -17,6 +17,7 @@ using System.Threading.Tasks;
 
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Services;
 
@@ -567,7 +568,9 @@ namespace MediaBrowser.Api.Images
 
             }).ToList() : new List<IImageEnhancer>();
 
-            var cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art;
+            var cropwhitespace = request.Type == ImageType.Logo || 
+                request.Type == ImageType.Art
+                || (request.Type == ImageType.Primary && item is LiveTvChannel);
 
             if (request.CropWhitespace.HasValue)
             {

+ 0 - 4
MediaBrowser.Api/Library/LibraryService.cs

@@ -512,10 +512,6 @@ namespace MediaBrowser.Api.Library
 
             var headers = new Dictionary<string, string>();
 
-            // Quotes are valid in linux. They'll possibly cause issues here
-            var filename = Path.GetFileName(item.Path).Replace("\"", string.Empty);
-            headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
-
             if (user != null)
             {
                 LogDownload(item, user, auth);

+ 11 - 9
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -22,6 +22,7 @@ using System.Threading.Tasks;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Diagnostics;
 
 namespace MediaBrowser.Api.Playback
@@ -100,11 +101,7 @@ namespace MediaBrowser.Api.Playback
         /// <summary>
         /// Gets the command line arguments.
         /// </summary>
-        /// <param name="outputPath">The output path.</param>
-        /// <param name="state">The state.</param>
-        /// <param name="isEncoding">if set to <c>true</c> [is encoding].</param>
-        /// <returns>System.String.</returns>
-        protected abstract string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding);
+        protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding);
 
         /// <summary>
         /// Gets the type of the transcoding job.
@@ -125,11 +122,11 @@ namespace MediaBrowser.Api.Playback
         /// <summary>
         /// Gets the output file path.
         /// </summary>
-        private string GetOutputFilePath(StreamState state, string outputFileExtension)
+        private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension)
         {
             var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
 
-            var data = GetCommandLineArguments("dummy\\dummy", state, false);
+            var data = GetCommandLineArguments("dummy\\dummy", encodingOptions, state, false);
 
             data += "-" + (state.Request.DeviceId ?? string.Empty);
             data += "-" + (state.Request.PlaySessionId ?? string.Empty);
@@ -217,8 +214,10 @@ namespace MediaBrowser.Api.Playback
                 }
             }
 
+            var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
+
             var transcodingId = Guid.NewGuid().ToString("N");
-            var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
+            var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true);
 
             var process = ApiEntryPoint.Instance.ProcessFactory.Create(new ProcessOptions
             {
@@ -826,7 +825,10 @@ namespace MediaBrowser.Api.Playback
             var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
                 ? GetOutputFileExtension(state)
                 : ("." + state.OutputContainer);
-            state.OutputFilePath = GetOutputFilePath(state, ext);
+
+            var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
+
+            state.OutputFilePath = GetOutputFilePath(state, encodingOptions, ext);
 
             return state;
         }

+ 9 - 13
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -14,6 +14,7 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Configuration;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
@@ -25,15 +26,12 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <summary>
         /// Gets the audio arguments.
         /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.String.</returns>
-        protected abstract string GetAudioArguments(StreamState state);
+        protected abstract string GetAudioArguments(StreamState state, EncodingOptions encodingOptions);
+
         /// <summary>
         /// Gets the video arguments.
         /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.String.</returns>
-        protected abstract string GetVideoArguments(StreamState state);
+        protected abstract string GetVideoArguments(StreamState state, EncodingOptions encodingOptions);
 
         /// <summary>
         /// Gets the segment file extension.
@@ -242,10 +240,8 @@ namespace MediaBrowser.Api.Playback.Hls
             }
         }
 
-        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
         {
-            var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
-
             var itsOffsetMs = 0;
 
             var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
@@ -285,8 +281,8 @@ namespace MediaBrowser.Api.Playback.Hls
                     EncodingHelper.GetInputArgument(state, encodingOptions),
                     threads,
                     EncodingHelper.GetMapArgs(state),
-                    GetVideoArguments(state),
-                    GetAudioArguments(state),
+                    GetVideoArguments(state, encodingOptions),
+                    GetAudioArguments(state, encodingOptions),
                     state.SegmentLength.ToString(UsCulture),
                     startNumberParam,
                     outputPath,
@@ -306,8 +302,8 @@ namespace MediaBrowser.Api.Playback.Hls
                 EncodingHelper.GetInputArgument(state, encodingOptions),
                 threads,
                 EncodingHelper.GetMapArgs(state),
-                GetVideoArguments(state),
-                GetAudioArguments(state),
+                GetVideoArguments(state, encodingOptions),
+                GetAudioArguments(state, encodingOptions),
                 state.SegmentLength.ToString(UsCulture),
                 startNumberParam,
                 state.HlsListSize.ToString(UsCulture),

+ 22 - 17
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -18,6 +18,7 @@ using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Services;
 using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
 
@@ -777,20 +778,20 @@ namespace MediaBrowser.Api.Playback.Hls
             return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
         }
 
-        protected override string GetAudioArguments(StreamState state)
+        protected override string GetAudioArguments(StreamState state, EncodingOptions encodingOptions)
         {
-            var codec = EncodingHelper.GetAudioEncoder(state);
+            var audioCodec = EncodingHelper.GetAudioEncoder(state);
 
             if (!state.IsOutputVideo)
             {
-                if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 {
                     return "-acodec copy";
                 }
 
                 var audioTranscodeParams = new List<string>();
 
-                audioTranscodeParams.Add("-acodec " + codec);
+                audioTranscodeParams.Add("-acodec " + audioCodec);
 
                 if (state.OutputAudioBitrate.HasValue)
                 {
@@ -811,12 +812,19 @@ namespace MediaBrowser.Api.Playback.Hls
                 return string.Join(" ", audioTranscodeParams.ToArray());
             }
 
-            if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase))
             {
-                return "-codec:a:0 copy -copypriorss:a:0 0";
+                var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
+
+                if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.EnableBreakOnNonKeyFrames(videoCodec))
+                {
+                    return "-codec:a:0 copy -copypriorss:a:0 0";
+                }
+
+                return "-codec:a:0 copy";
             }
 
-            var args = "-codec:a:0 " + codec;
+            var args = "-codec:a:0 " + audioCodec;
 
             var channels = state.OutputAudioChannels;
 
@@ -837,19 +845,19 @@ namespace MediaBrowser.Api.Playback.Hls
                 args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture);
             }
 
-            args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), true);
+            args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true);
 
             return args;
         }
 
-        protected override string GetVideoArguments(StreamState state)
+        protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions)
         {
             if (!state.IsOutputVideo)
             {
                 return string.Empty;
             }
 
-            var codec = EncodingHelper.GetVideoEncoder(state, ApiEntryPoint.Instance.GetEncodingOptions());
+            var codec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
 
             var args = "-codec:v:0 " + codec;
 
@@ -875,8 +883,6 @@ namespace MediaBrowser.Api.Playback.Hls
 
                 var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
 
-                var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
-
                 args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
 
                 //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
@@ -911,9 +917,8 @@ namespace MediaBrowser.Api.Playback.Hls
             return args;
         }
 
-        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
         {
-            var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
             var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false);
 
             var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
@@ -940,7 +945,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 segmentFormat = "mpegts";
             }
 
-            var videoCodec = EncodingHelper.GetVideoEncoder(state, ApiEntryPoint.Instance.GetEncodingOptions());
+            var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
             var breakOnNonKeyFrames = state.EnableBreakOnNonKeyFrames(videoCodec);
 
             var breakOnNonKeyFramesArg = breakOnNonKeyFrames ? " -break_non_keyframes 1" : "";
@@ -950,8 +955,8 @@ namespace MediaBrowser.Api.Playback.Hls
                 EncodingHelper.GetInputArgument(state, encodingOptions),
                 threads,
                 mapArgs,
-                GetVideoArguments(state),
-                GetAudioArguments(state),
+                GetVideoArguments(state, encodingOptions),
+                GetAudioArguments(state, encodingOptions),
                 state.SegmentLength.ToString(UsCulture),
                 startNumberParam,
                 outputPath,

+ 10 - 9
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -7,6 +7,7 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 using System;
 using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Services;
 
@@ -31,9 +32,7 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <summary>
         /// Gets the audio arguments.
         /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.String.</returns>
-        protected override string GetAudioArguments(StreamState state)
+        protected override string GetAudioArguments(StreamState state, EncodingOptions encodingOptions)
         {
             var codec = EncodingHelper.GetAudioEncoder(state);
 
@@ -63,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture);
             }
 
-            args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), true);
+            args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true);
 
             return args;
         }
@@ -71,11 +70,14 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <summary>
         /// Gets the video arguments.
         /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.String.</returns>
-        protected override string GetVideoArguments(StreamState state)
+        protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions)
         {
-            var codec = EncodingHelper.GetVideoEncoder(state, ApiEntryPoint.Instance.GetEncodingOptions());
+            if (!state.IsOutputVideo)
+            {
+                return string.Empty;
+            }
+
+            var codec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
 
             var args = "-codec:v:0 " + codec;
 
@@ -101,7 +103,6 @@ namespace MediaBrowser.Api.Playback.Hls
 
                 var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
 
-                var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
                 args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
 
                 // Add resolution params, if specified

+ 4 - 4
MediaBrowser.Api/Playback/MediaInfoService.cs

@@ -134,7 +134,7 @@ namespace MediaBrowser.Api.Playback
 
                 SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
                     request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
-                    request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, request.EnableDirectPlay, request.ForceDirectPlayRemoteMediaSource, request.EnableDirectStream, true, true, true);
+                    request.SubtitleStreamIndex, request.MaxAudioChannels, request.PlaySessionId, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, true, true, true);
             }
             else
             {
@@ -176,7 +176,7 @@ namespace MediaBrowser.Api.Playback
             {
                 var mediaSourceId = request.MediaSourceId;
 
-                SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, request.ForceDirectPlayRemoteMediaSource, request.EnableDirectStream, request.EnableTranscoding, request.AllowVideoStreamCopy, request.AllowAudioStreamCopy);
+                SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.MaxAudioChannels, request.UserId, request.EnableDirectPlay, true, request.EnableDirectStream, request.EnableTranscoding, request.AllowVideoStreamCopy, request.AllowAudioStreamCopy);
             }
 
             if (request.AutoOpenLiveStream)
@@ -191,7 +191,6 @@ namespace MediaBrowser.Api.Playback
                         DeviceProfile = request.DeviceProfile,
                         EnableDirectPlay = request.EnableDirectPlay,
                         EnableDirectStream = request.EnableDirectStream,
-                        ForceDirectPlayRemoteMediaSource = request.ForceDirectPlayRemoteMediaSource,
                         ItemId = request.Id,
                         MaxAudioChannels = request.MaxAudioChannels,
                         MaxStreamingBitrate = request.MaxStreamingBitrate,
@@ -199,7 +198,8 @@ namespace MediaBrowser.Api.Playback
                         StartTimeTicks = request.StartTimeTicks,
                         SubtitleStreamIndex = request.SubtitleStreamIndex,
                         UserId = request.UserId,
-                        OpenToken = mediaSource.OpenToken
+                        OpenToken = mediaSource.OpenToken,
+                        EnableMediaProbe = request.EnableMediaProbe
 
                     }).ConfigureAwait(false);
 

+ 2 - 3
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -10,6 +10,7 @@ using MediaBrowser.Model.Serialization;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.System;
@@ -58,10 +59,8 @@ namespace MediaBrowser.Api.Playback.Progressive
             return ProcessRequest(request, true);
         }
 
-        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
         {
-            var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
-
             return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);
         }
     }

+ 2 - 3
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Services;
 using MediaBrowser.Model.System;
 
@@ -91,10 +92,8 @@ namespace MediaBrowser.Api.Playback.Progressive
             return ProcessRequest(request, true);
         }
 
-        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
+        protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
         {
-            var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
-
             return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultH264Preset());
         }
     }

+ 1 - 1
MediaBrowser.Api/Playback/StreamState.cs

@@ -80,7 +80,7 @@ namespace MediaBrowser.Api.Playback
                             return 6;
                         }
 
-                        return 10;
+                        return 6;
                     }
 
                     if (IsSegmentedLiveStream)

+ 1 - 1
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -224,7 +224,7 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string ParentId { get; set; }
 
-        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
 
         [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]

+ 15 - 1
MediaBrowser.Common/Extensions/ResourceNotFoundException.cs

@@ -12,7 +12,7 @@ namespace MediaBrowser.Common.Extensions
         /// </summary>
         public ResourceNotFoundException()
         {
-            
+
         }
 
         /// <summary>
@@ -26,6 +26,20 @@ namespace MediaBrowser.Common.Extensions
         }
     }
 
+    public class RemoteServiceUnavailableException : Exception
+    {
+        public RemoteServiceUnavailableException()
+        {
+
+        }
+
+        public RemoteServiceUnavailableException(string message)
+            : base(message)
+        {
+
+        }
+    }
+
     public class RateLimitExceededException : Exception
     {
         /// <summary>

+ 1 - 1
MediaBrowser.Controller/Channels/ChannelMediaInfo.cs

@@ -67,7 +67,7 @@ namespace MediaBrowser.Controller.Channels
                 Name = id,
                 Id = id,
                 ReadAtNativeFramerate = ReadAtNativeFramerate,
-                SupportsDirectStream = false,
+                SupportsDirectStream = Protocol == MediaProtocol.Http && !string.IsNullOrWhiteSpace(Container) && !string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase),
                 SupportsDirectPlay = SupportsDirectPlay,
                 IsRemote = true
             };

+ 2 - 9
MediaBrowser.Controller/Dlna/IEventManager.cs

@@ -12,18 +12,11 @@ namespace MediaBrowser.Controller.Dlna
         /// <summary>
         /// Renews the event subscription.
         /// </summary>
-        /// <param name="subscriptionId">The subscription identifier.</param>
-        /// <param name="timeoutSeconds">The timeout seconds.</param>
-        /// <returns>EventSubscriptionResponse.</returns>
-        EventSubscriptionResponse RenewEventSubscription(string subscriptionId, int? timeoutSeconds);
+        EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string requestedTimeoutString);
 
         /// <summary>
         /// Creates the event subscription.
         /// </summary>
-        /// <param name="notificationType">Type of the notification.</param>
-        /// <param name="timeoutSeconds">The timeout seconds.</param>
-        /// <param name="callbackUrl">The callback URL.</param>
-        /// <returns>EventSubscriptionResponse.</returns>
-        EventSubscriptionResponse CreateEventSubscription(string notificationType, int? timeoutSeconds, string callbackUrl);
+        EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl);
     }
 }

+ 16 - 0
MediaBrowser.Controller/Entities/Video.cs

@@ -57,6 +57,22 @@ namespace MediaBrowser.Controller.Entities
         {
             get
             {
+                var extraType = ExtraType;
+                if (extraType.HasValue)
+                {
+                    if (extraType.Value == Model.Entities.ExtraType.Sample)
+                    {
+                        return false;
+                    }
+                    if (extraType.Value == Model.Entities.ExtraType.ThemeVideo)
+                    {
+                        return false;
+                    }
+                    if (extraType.Value == Model.Entities.ExtraType.Trailer)
+                    {
+                        return false;
+                    }
+                }
                 return true;
             }
         }

+ 1 - 4
MediaBrowser.Controller/Library/IMediaSourceProvider.cs

@@ -20,10 +20,7 @@ namespace MediaBrowser.Controller.Library
         /// <summary>
         /// Opens the media source.
         /// </summary>
-        /// <param name="openToken">The open token.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task&lt;MediaSourceInfo&gt;.</returns>
-        Task<Tuple<MediaSourceInfo,IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken);
+        Task<Tuple<MediaSourceInfo,IDirectStreamProvider>> OpenMediaSource(string openToken, bool allowLiveStreamProbe, CancellationToken cancellationToken);
 
         /// <summary>
         /// Closes the media source.

+ 5 - 1
MediaBrowser.Controller/Providers/DirectoryService.cs

@@ -120,10 +120,14 @@ namespace MediaBrowser.Controller.Providers
             {
                 file = _fileSystem.GetFileInfo(path);
 
-                if (file != null)
+                if (file != null && file.Exists)
                 {
                     _fileCache.TryAdd(path, file);
                 }
+                else
+                {
+                    return null;
+                }
             }
 
             return file;

+ 14 - 0
MediaBrowser.Controller/Session/SessionInfo.cs

@@ -203,6 +203,11 @@ namespace MediaBrowser.Controller.Session
 
         public void StartAutomaticProgress(ITimerFactory timerFactory, PlaybackProgressInfo progressInfo)
         {
+            if (_disposed)
+            {
+                return;
+            }
+
             lock (_progressLock)
             {
                 _lastProgressInfo = progressInfo;
@@ -223,6 +228,11 @@ namespace MediaBrowser.Controller.Session
 
         private async void OnProgressTimerCallback(object state)
         {
+            if (_disposed)
+            {
+                return;
+            }
+
             var progressInfo = _lastProgressInfo;
             if (progressInfo == null)
             {
@@ -274,8 +284,12 @@ namespace MediaBrowser.Controller.Session
             }
         }
 
+        private bool _disposed = false;
+
         public void Dispose()
         {
+            _disposed = true;
+
             StopAutomaticProgress();
             _sessionManager = null;
         }

+ 2 - 0
MediaBrowser.Controller/Sync/ISyncManager.cs

@@ -90,6 +90,8 @@ namespace MediaBrowser.Controller.Sync
         /// </summary>
         IEnumerable<SyncTarget> GetSyncTargets(string userId);
 
+        IEnumerable<SyncTarget> GetSyncTargets(string userId, bool? supportsRemoteSync);
+
         /// <summary>
         /// Supportses the synchronize.
         /// </summary>

+ 3 - 1
MediaBrowser.Controller/Sync/ISyncProvider.cs

@@ -11,6 +11,8 @@ namespace MediaBrowser.Controller.Sync
         /// <value>The name.</value>
         string Name { get; }
 
+        bool SupportsRemoteSync { get; }
+
         /// <summary>
         /// Gets the synchronize targets.
         /// </summary>
@@ -27,6 +29,6 @@ namespace MediaBrowser.Controller.Sync
 
     public interface IHasUniqueTargetIds
     {
-        
+
     }
 }

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

@@ -733,9 +733,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
             var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
 
             var enableThumbnail = !new List<string> { "wtv" }.Contains(container ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+            // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
             var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty;
 
-            // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
             var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) :
                 string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
 

+ 16 - 2
MediaBrowser.Model/Dlna/DirectPlayProfile.cs

@@ -1,6 +1,7 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Linq;
 using System.Xml.Serialization;
-using MediaBrowser.Model.Dlna;
 
 namespace MediaBrowser.Model.Dlna
 {
@@ -28,6 +29,19 @@ namespace MediaBrowser.Model.Dlna
             return list;
         }
 
+        public bool SupportsContainer(string container)
+        {
+            var all = GetContainers();
+
+            // Only allow unknown container if the profile is all inclusive
+            if (string.IsNullOrWhiteSpace(container))
+            {
+                return all.Count == 0;
+            }
+
+            return all.Count == 0 || all.Contains(container, StringComparer.OrdinalIgnoreCase);
+        }
+
         public List<string> GetAudioCodecs()
         {
             List<string> list = new List<string>();

+ 60 - 74
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -492,52 +492,45 @@ namespace MediaBrowser.Model.Dlna
 
             foreach (var profile in directPlayProfiles)
             {
-                if (profile.Container.Length > 0)
+                // Check container type
+                if (profile.SupportsContainer(item.Container))
                 {
-                    // Check container type
-                    string mediaContainer = item.Container ?? string.Empty;
-                    foreach (string i in profile.GetContainers())
+                    containerSupported = true;
+
+                    if (videoStream != null)
                     {
-                        if (StringHelper.EqualsIgnoreCase(i, mediaContainer))
+                        // Check video codec
+                        List<string> videoCodecs = profile.GetVideoCodecs();
+                        if (videoCodecs.Count > 0)
                         {
-                            containerSupported = true;
-
-                            if (videoStream != null)
+                            string videoCodec = videoStream.Codec;
+                            if (!string.IsNullOrEmpty(videoCodec) && ListHelper.ContainsIgnoreCase(videoCodecs, videoCodec))
                             {
-                                // Check video codec
-                                List<string> videoCodecs = profile.GetVideoCodecs();
-                                if (videoCodecs.Count > 0)
-                                {
-                                    string videoCodec = videoStream.Codec;
-                                    if (!string.IsNullOrEmpty(videoCodec) && ListHelper.ContainsIgnoreCase(videoCodecs, videoCodec))
-                                    {
-                                        videoSupported = true;
-                                    }
-                                }
-                                else
-                                {
-                                    videoSupported = true;
-                                }
+                                videoSupported = true;
                             }
+                        }
+                        else
+                        {
+                            videoSupported = true;
+                        }
+                    }
 
-                            if (audioStream != null)
+                    if (audioStream != null)
+                    {
+                        // Check audio codec
+                        List<string> audioCodecs = profile.GetAudioCodecs();
+                        if (audioCodecs.Count > 0)
+                        {
+                            string audioCodec = audioStream.Codec;
+                            if (!string.IsNullOrEmpty(audioCodec) && ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec))
                             {
-                                // Check audio codec
-                                List<string> audioCodecs = profile.GetAudioCodecs();
-                                if (audioCodecs.Count > 0)
-                                {
-                                    string audioCodec = audioStream.Codec;
-                                    if (!string.IsNullOrEmpty(audioCodec) && ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec))
-                                    {
-                                        audioSupported = true;
-                                    }
-                                }
-                                else
-                                {
-                                    audioSupported = true;
-                                }
+                                audioSupported = true;
                             }
                         }
+                        else
+                        {
+                            audioSupported = true;
+                        }
                     }
                 }
             }
@@ -635,8 +628,10 @@ namespace MediaBrowser.Model.Dlna
             MediaStream videoStream = item.VideoStream;
 
             // TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
-            bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true), subtitleStream, options, PlayMethod.DirectPlay));
-            bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || IsEligibleForDirectPlay(item, options.GetMaxBitrate(false), subtitleStream, options, PlayMethod.DirectStream));
+            var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true), subtitleStream, options, PlayMethod.DirectPlay);
+            var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false), subtitleStream, options, PlayMethod.DirectStream);
+            bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1);
+            bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.Item1);
 
             _logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
                 options.Profile.Name ?? "Unknown Profile",
@@ -669,6 +664,16 @@ namespace MediaBrowser.Model.Dlna
                 transcodeReasons.AddRange(directPlayInfo.Item2);
             }
 
+            if (directPlayEligibilityResult.Item2.HasValue)
+            {
+                transcodeReasons.Add(directPlayEligibilityResult.Item2.Value);
+            }
+
+            if (directStreamEligibilityResult.Item2.HasValue)
+            {
+                transcodeReasons.Add(directStreamEligibilityResult.Item2.Value);
+            }
+
             // Can't direct play, find the transcoding profile
             TranscodingProfile transcodingProfile = null;
             foreach (TranscodingProfile i in options.Profile.TranscodingProfiles)
@@ -1136,7 +1141,7 @@ namespace MediaBrowser.Model.Dlna
                 mediaSource.Path ?? "Unknown path");
         }
 
-        private bool IsEligibleForDirectPlay(MediaSourceInfo item,
+        private Tuple<bool, TranscodeReason?> IsEligibleForDirectPlay(MediaSourceInfo item,
             long? maxBitrate,
             MediaStream subtitleStream,
             VideoOptions options,
@@ -1149,11 +1154,18 @@ namespace MediaBrowser.Model.Dlna
                 if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
                 {
                     _logger.Info("Not eligible for {0} due to unsupported subtitles", playMethod);
-                    return false;
+                    return new Tuple<bool, TranscodeReason?>(false, TranscodeReason.SubtitleCodecNotSupported);
                 }
             }
 
-            return IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
+            var result = IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod);
+
+            if (result)
+            {
+                return new Tuple<bool, TranscodeReason?>(result, null);
+            }
+
+            return new Tuple<bool, TranscodeReason?>(result, TranscodeReason.ContainerBitrateExceedsLimit);
         }
 
         public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, string transcodingSubProtocol, string transcodingContainer)
@@ -1519,23 +1531,10 @@ namespace MediaBrowser.Model.Dlna
 
         private bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
         {
-            if (profile.Container.Length > 0)
+            // Check container type
+            if (!profile.SupportsContainer(item.Container))
             {
-                // Check container type
-                string mediaContainer = item.Container ?? string.Empty;
-                bool any = false;
-                foreach (string i in profile.GetContainers())
-                {
-                    if (StringHelper.EqualsIgnoreCase(i, mediaContainer))
-                    {
-                        any = true;
-                        break;
-                    }
-                }
-                if (!any)
-                {
-                    return false;
-                }
+                return false;
             }
 
             // Check audio codec
@@ -1555,23 +1554,10 @@ namespace MediaBrowser.Model.Dlna
 
         private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream)
         {
-            if (profile.Container.Length > 0)
+            // Check container type
+            if (!profile.SupportsContainer(item.Container))
             {
-                // Check container type
-                string mediaContainer = item.Container ?? string.Empty;
-                bool any = false;
-                foreach (string i in profile.GetContainers())
-                {
-                    if (StringHelper.EqualsIgnoreCase(i, mediaContainer))
-                    {
-                        any = true;
-                        break;
-                    }
-                }
-                if (!any)
-                {
-                    return false;
-                }
+                return false;
             }
 
             // Check video codec

+ 2 - 2
MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs

@@ -17,13 +17,13 @@ namespace MediaBrowser.Model.MediaInfo
 
         public bool EnableDirectPlay { get; set; }
         public bool EnableDirectStream { get; set; }
-        public bool ForceDirectPlayRemoteMediaSource { get; set; }
+        public bool EnableMediaProbe { get; set; }
 
         public LiveStreamRequest()
         {
-            ForceDirectPlayRemoteMediaSource = true;
             EnableDirectPlay = true;
             EnableDirectStream = true;
+            EnableMediaProbe = true;
         }
 
         public LiveStreamRequest(AudioOptions options)

+ 2 - 2
MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs

@@ -27,19 +27,19 @@ namespace MediaBrowser.Model.MediaInfo
         public bool EnableDirectPlay { get; set; }
         public bool EnableDirectStream { get; set; }
         public bool EnableTranscoding { get; set; }
-        public bool ForceDirectPlayRemoteMediaSource { get; set; }
         public bool AllowVideoStreamCopy { get; set; }
         public bool AllowAudioStreamCopy { get; set; }
         public bool AutoOpenLiveStream { get; set; }
+        public bool EnableMediaProbe { get; set; }
 
         public PlaybackInfoRequest()
         {
-            ForceDirectPlayRemoteMediaSource = true;
             EnableDirectPlay = true;
             EnableDirectStream = true;
             EnableTranscoding = true;
             AllowVideoStreamCopy = true;
             AllowAudioStreamCopy = true;
+            EnableMediaProbe = true;
         }
     }
 }

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

@@ -48,6 +48,7 @@ namespace MediaBrowser.Model.Session
         VideoFramerateNotSupported = 17,
         VideoLevelNotSupported = 18,
         VideoProfileNotSupported = 19,
-        AudioBitDepthNotSupported = 20
+        AudioBitDepthNotSupported = 20,
+        SubtitleCodecNotSupported = 21
     }
 }

+ 4 - 1
MediaBrowser.Model/Sync/SyncJob.cs

@@ -59,7 +59,7 @@ namespace MediaBrowser.Model.Sync
         /// Gets or sets the status.
         /// </summary>
         /// <value>The status.</value>
-        public SyncJobStatus Status { get; set; }        
+        public SyncJobStatus Status { get; set; }
         /// <summary>
         /// Gets or sets the user identifier.
         /// </summary>
@@ -105,9 +105,12 @@ namespace MediaBrowser.Model.Sync
         public string PrimaryImageItemId { get; set; }
         public string PrimaryImageTag { get; set; }
 
+        public bool EnableAutomaticResync { get; set; }
+
         public SyncJob()
         {
             RequestedItemIds = new List<string>();
+            EnableAutomaticResync = true;
         }
     }
 }

+ 9 - 12
MediaBrowser.Providers/Music/ArtistMetadataService.cs

@@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Music
 
             if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
             {
-                if (!item.IsLocked)
+                if (!item.IsLocked && !item.LockedFields.Contains(MetadataFields.Genres))
                 {
                     var taggedItems = item.IsAccessedByName ?
                         item.GetTaggedItems(new Controller.Entities.InternalItemsQuery()
@@ -31,22 +31,19 @@ namespace MediaBrowser.Providers.Music
                         }) :
                         item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder).ToList();
 
-                    if (!item.LockedFields.Contains(MetadataFields.Genres))
-                    {
-                        var currentList = item.Genres.ToList();
+                    var currentList = item.Genres.ToList();
 
-                        item.Genres = taggedItems.SelectMany(i => i.Genres)
-                            .DistinctNames()
-                            .ToList();
+                    item.Genres = taggedItems.SelectMany(i => i.Genres)
+                        .DistinctNames()
+                        .ToList();
 
-                        if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
-                        {
-                            updateType = updateType | ItemUpdateType.MetadataEdit;
-                        }
+                    if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
+                    {
+                        updateType = updateType | ItemUpdateType.MetadataEdit;
                     }
                 }
             }
-            
+
             return updateType;
         }
 

+ 30 - 2
MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using System;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Controller.Providers;
@@ -6,7 +7,7 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using System.Collections.Generic;
-
+using System.Linq;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.IO;
 
@@ -33,6 +34,33 @@ namespace MediaBrowser.Providers.Playlists
             }
         }
 
+        protected override ItemUpdateType BeforeSave(Playlist item, bool isFullRefresh, ItemUpdateType currentUpdateType)
+        {
+            var updateType = base.BeforeSave(item, isFullRefresh, currentUpdateType);
+
+            if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
+            {
+                if (!item.IsLocked && !item.LockedFields.Contains(MetadataFields.Genres))
+                {
+                    var items = item.GetLinkedChildren()
+                        .ToList();
+
+                    var currentList = item.Genres.ToList();
+
+                    item.Genres = items.SelectMany(i => i.Genres)
+                        .Distinct(StringComparer.OrdinalIgnoreCase)
+                        .ToList();
+
+                    if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
+                    {
+                        updateType = updateType | ItemUpdateType.MetadataEdit;
+                    }
+                }
+            }
+
+            return updateType;
+        }
+
         public PlaylistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
         {
         }

+ 0 - 2
MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs

@@ -196,8 +196,6 @@ namespace MediaBrowser.Providers.TV
             }
             catch (HttpException ex)
             {
-                Logger.Error("No metadata found for {0}", seasonNumber.Value);
-
                 if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
                 {
                     return result;

+ 29 - 89
MediaBrowser.Server.Mac/Emby.Server.Mac.csproj

@@ -116,10 +116,10 @@
       <HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath>
     </Reference>
     <Reference Include="SQLitePCLRaw.core">
-      <HintPath>..\packages\SQLitePCLRaw.core.1.1.6\lib\net45\SQLitePCLRaw.core.dll</HintPath>
+      <HintPath>..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll</HintPath>
     </Reference>
     <Reference Include="SQLitePCLRaw.provider.sqlite3">
-      <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.6\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
+      <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.7\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
     </Reference>
     <Reference Include="Emby.Server.Connect">
       <HintPath>..\ThirdParty\emby\Emby.Server.Connect.dll</HintPath>
@@ -411,6 +411,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\mypreferencesmenu.html">
       <Link>Resources\dashboard-ui\mypreferencesmenu.html</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\mypreferencessubtitles.html">
+      <Link>Resources\dashboard-ui\mypreferencessubtitles.html</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\myprofile.html">
       <Link>Resources\dashboard-ui\myprofile.html</Link>
     </BundleResource>
@@ -918,6 +921,12 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-radio\emby-radio.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-radio\emby-radio.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-scrollbuttons\emby-scrollbuttons.css">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-scrollbuttons\emby-scrollbuttons.css</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-scrollbuttons\emby-scrollbuttons.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-scrollbuttons\emby-scrollbuttons.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-scroller\emby-scroller.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-scroller\emby-scroller.js</Link>
     </BundleResource>
@@ -1476,6 +1485,15 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.template.html">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.template.html</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\subtitlesettings\subtitleappearancehelper.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\subtitlesettings\subtitleappearancehelper.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\subtitlesettings\subtitlesettings.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\subtitlesettings\subtitlesettings.js</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\subtitlesettings\subtitlesettings.template.html">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\subtitlesettings\subtitlesettings.template.html</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sync\emby-downloadbutton.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sync\emby-downloadbutton.js</Link>
     </BundleResource>
@@ -1488,15 +1506,18 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sync\syncjoblist.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sync\syncjoblist.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sync\synctoggle.js">
-      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sync\synctoggle.js</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\toast\toast.css">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\toast\toast.css</Link>
     </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\toast\toast.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\toast\toast.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\upnextdialog\upnextdialog.css">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\upnextdialog\upnextdialog.css</Link>
+    </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\upnextdialog\upnextdialog.js">
+      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\upnextdialog\upnextdialog.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\userdatabuttons\emby-playstatebutton.js">
       <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\userdatabuttons\emby-playstatebutton.js</Link>
     </BundleResource>
@@ -1788,9 +1809,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\librarybrowser.css">
       <Link>Resources\dashboard-ui\css\librarybrowser.css</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\librarymenu.css">
-      <Link>Resources\dashboard-ui\css\librarymenu.css</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\livetv.css">
       <Link>Resources\dashboard-ui\css\livetv.css</Link>
     </BundleResource>
@@ -2082,84 +2100,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\ios\ios.css">
       <Link>Resources\dashboard-ui\devices\ios\ios.css</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\-l14jk06m6puhb-5mxqqnrjtnkitppoi_ivcxxdnrsc.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\-l14jk06m6puhb-5mxqqnrjtnkitppoi_ivcxxdnrsc.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\0ec6fl06luxeywpbsjvxcbjtnkitppoi_ivcxxdnrsc.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\0ec6fl06luxeywpbsjvxcbjtnkitppoi_ivcxxdnrsc.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\2tsd397wlxj96qwhynikxpeszw2xoq-xsnqo47m55da.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\2tsd397wlxj96qwhynikxpeszw2xoq-xsnqo47m55da.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\97uahxiqzroncbacei3awxjtnkitppoi_ivcxxdnrsc.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\97uahxiqzroncbacei3awxjtnkitppoi_ivcxxdnrsc.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\azmswpodyevhtrvuabjwvbtbgvql8ndjpwnre27mub0.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\azmswpodyevhtrvuabjwvbtbgvql8ndjpwnre27mub0.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\cwb0xya8bzo0ksthx0utua.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\cwb0xya8bzo0ksthx0utua.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\d-6iyplofoccackzxwxsoftxra8tvwticgirnjhmvjw.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\d-6iyplofoccackzxwxsoftxra8tvwticgirnjhmvjw.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\e7mevayvogmqfwwl61pkhbtbgvql8ndjpwnre27mub0.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\e7mevayvogmqfwwl61pkhbtbgvql8ndjpwnre27mub0.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\fcx7wwv8ozt71a3e1xoajveszw2xoq-xsnqo47m55da.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\fcx7wwv8ozt71a3e1xoajveszw2xoq-xsnqo47m55da.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\fl4y0qdoxyythegmxx8kcrjtnkitppoi_ivcxxdnrsc.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\fl4y0qdoxyythegmxx8kcrjtnkitppoi_ivcxxdnrsc.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\frnv30oaydlfrth2vnzzdhtbgvql8ndjpwnre27mub0.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\frnv30oaydlfrth2vnzzdhtbgvql8ndjpwnre27mub0.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\gwvjdern2amz39wrsoz7fxtbgvql8ndjpwnre27mub0.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\gwvjdern2amz39wrsoz7fxtbgvql8ndjpwnre27mub0.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\hgo13k-tfspn0qi1sfdufvtxra8tvwticgirnjhmvjw.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\hgo13k-tfspn0qi1sfdufvtxra8tvwticgirnjhmvjw.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\i3s1wsgsg9ycurv6puktorjtnkitppoi_ivcxxdnrsc.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\i3s1wsgsg9ycurv6puktorjtnkitppoi_ivcxxdnrsc.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\nydwbdd4giq26g5xybhsfbjtnkitppoi_ivcxxdnrsc.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\nydwbdd4giq26g5xybhsfbjtnkitppoi_ivcxxdnrsc.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\ooefwznlrtefzlymlvv1ubjtnkitppoi_ivcxxdnrsc.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\ooefwznlrtefzlymlvv1ubjtnkitppoi_ivcxxdnrsc.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\pru33qjshpzsmg3z6vywnrjtnkitppoi_ivcxxdnrsc.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\pru33qjshpzsmg3z6vywnrjtnkitppoi_ivcxxdnrsc.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\robotobold.woff">
-      <Link>Resources\dashboard-ui\fonts\roboto\robotobold.woff</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\robotolight.woff">
-      <Link>Resources\dashboard-ui\fonts\roboto\robotolight.woff</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\robotomedium.woff">
-      <Link>Resources\dashboard-ui\fonts\roboto\robotomedium.woff</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\robotoregular.woff">
-      <Link>Resources\dashboard-ui\fonts\roboto\robotoregular.woff</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\robotothin.woff">
-      <Link>Resources\dashboard-ui\fonts\roboto\robotothin.woff</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\rxzjdnzeo3r5zsexge8uuvtxra8tvwticgirnjhmvjw.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\rxzjdnzeo3r5zsexge8uuvtxra8tvwticgirnjhmvjw.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\style.css">
-      <Link>Resources\dashboard-ui\fonts\roboto\style.css</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\ty9dfvlaziwdqq2dhoyjphtbgvql8ndjpwnre27mub0.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\ty9dfvlaziwdqq2dhoyjphtbgvql8ndjpwnre27mub0.woff2</Link>
-    </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\fonts\roboto\vvxugkzxbhtx_s_vctlpghtbgvql8ndjpwnre27mub0.woff2">
-      <Link>Resources\dashboard-ui\fonts\roboto\vvxugkzxbhtx_s_vctlpghtbgvql8ndjpwnre27mub0.woff2</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\buttonenabled.js">
       <Link>Resources\dashboard-ui\legacy\buttonenabled.js</Link>
     </BundleResource>
@@ -2358,6 +2298,9 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\mypreferenceslanguages.js">
       <Link>Resources\dashboard-ui\scripts\mypreferenceslanguages.js</Link>
     </BundleResource>
+    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\mypreferencessubtitles.js">
+      <Link>Resources\dashboard-ui\scripts\mypreferencessubtitles.js</Link>
+    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\myprofile.js">
       <Link>Resources\dashboard-ui\scripts\myprofile.js</Link>
     </BundleResource>
@@ -2658,9 +2601,6 @@
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\themes\holiday\theme.js">
       <Link>Resources\dashboard-ui\themes\holiday\theme.js</Link>
     </BundleResource>
-    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\paper-button-style.css">
-      <Link>Resources\dashboard-ui\thirdparty\paper-button-style.css</Link>
-    </BundleResource>
     <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.listview.css">
       <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.listview.css</Link>
     </BundleResource>

+ 2 - 2
MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj

@@ -71,11 +71,11 @@
       <Private>True</Private>
     </Reference>
     <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
-      <HintPath>..\packages\SQLitePCLRaw.core.1.1.6\lib\net45\SQLitePCLRaw.core.dll</HintPath>
+      <HintPath>..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="SQLitePCLRaw.provider.sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=62684c7b4f184e3f, processorArchitecture=MSIL">
-      <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.6\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
+      <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.7\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="System" />

+ 2 - 2
MediaBrowser.Server.Mono/packages.config

@@ -6,6 +6,6 @@
   <package id="SharpCompress" version="0.14.0" targetFramework="net46" />
   <package id="SimpleInjector" version="4.0.8" targetFramework="net46" />
   <package id="SkiaSharp" version="1.58.0" targetFramework="net46" />
-  <package id="SQLitePCLRaw.core" version="1.1.6" targetFramework="net46" />
-  <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.6" targetFramework="net46" />
+  <package id="SQLitePCLRaw.core" version="1.1.7" targetFramework="net46" />
+  <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.7" targetFramework="net46" />
 </packages>

+ 34 - 10
MediaBrowser.ServerApplication/MainStartup.cs

@@ -678,7 +678,7 @@ namespace MediaBrowser.ServerApplication
 
             _logger.Info("Calling Application.Exit");
             //Application.Exit();
-            
+
             Environment.Exit(0);
         }
 
@@ -770,19 +770,43 @@ namespace MediaBrowser.ServerApplication
 
             try
             {
-                var subkey = Environment.Is64BitProcess
-                    ? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64"
-                    : "SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x86";
+                RegistryKey key;
 
-                using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
-                    .OpenSubKey(subkey))
+                if (Environment.Is64BitProcess)
                 {
-                    if (ndpKey != null && ndpKey.GetValue("Version") != null)
+                    key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
+                       .OpenSubKey("SOFTWARE\\Classes\\Installer\\Dependencies\\{d992c12e-cab2-426f-bde3-fb8c53950b0d}");
+
+                    if (key == null)
                     {
-                        var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v');
-                        if (installedVersion.StartsWith("14", StringComparison.OrdinalIgnoreCase))
+                        key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
+                            .OpenSubKey("SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64");
+                    }
+                }
+                else
+                {
+                    key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
+                        .OpenSubKey("SOFTWARE\\Classes\\Installer\\Dependencies\\{e2803110-78b3-4664-a479-3611a381656a}");
+
+                    if (key == null)
+                    {
+                        key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
+                            .OpenSubKey("SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x86");
+                    }
+                }
+
+                if (key != null)
+                {
+                    using (key)
+                    {
+                        var version = key.GetValue("Version");
+                        if (version != null)
                         {
-                            return;
+                            var installedVersion = ((string)version).TrimStart('v');
+                            if (installedVersion.StartsWith("14", StringComparison.OrdinalIgnoreCase))
+                            {
+                                return;
+                            }
                         }
                     }
                 }

+ 2 - 6
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

@@ -73,10 +73,6 @@
     <Reference Include="Emby.Server.Sync">
       <HintPath>..\ThirdParty\emby\Emby.Server.Sync.dll</HintPath>
     </Reference>
-    <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ImageMagickSharp.1.0.0.18\lib\net45\ImageMagickSharp.dll</HintPath>
-    </Reference>
     <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
       <HintPath>..\packages\NLog.4.4.11\lib\net45\NLog.dll</HintPath>
       <Private>True</Private>
@@ -96,11 +92,11 @@
       <HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath>
     </Reference>
     <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
-      <HintPath>..\packages\SQLitePCLRaw.core.1.1.6\lib\net45\SQLitePCLRaw.core.dll</HintPath>
+      <HintPath>..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="SQLitePCLRaw.provider.sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=62684c7b4f184e3f, processorArchitecture=MSIL">
-      <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.6\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
+      <HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.7\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="System" />

+ 2 - 3
MediaBrowser.ServerApplication/packages.config

@@ -1,11 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
-  <package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" />
   <package id="NLog" version="4.4.11" targetFramework="net462" />
   <package id="ServiceStack.Text" version="4.5.8" targetFramework="net462" />
   <package id="SharpCompress" version="0.14.0" targetFramework="net462" />
   <package id="SimpleInjector" version="4.0.8" targetFramework="net462" />
   <package id="SkiaSharp" version="1.58.0" targetFramework="net462" />
-  <package id="SQLitePCLRaw.core" version="1.1.6" targetFramework="net462" />
-  <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.6" targetFramework="net462" />
+  <package id="SQLitePCLRaw.core" version="1.1.7" targetFramework="net462" />
+  <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.7" targetFramework="net462" />
 </packages>

+ 1 - 4
MediaBrowser.WebDashboard/Api/PackageCreator.cs

@@ -242,10 +242,7 @@ namespace MediaBrowser.WebDashboard.Api
 
             var files = new[]
                             {
-                                      "css/site.css" + versionString,
-                                      "css/librarymenu.css" + versionString,
-                                      "css/librarybrowser.css" + versionString,
-                                      "thirdparty/paper-button-style.css" + versionString
+                                      "css/site.css" + versionString
                             };
 
             var tags = files.Select(s => string.Format("<link rel=\"stylesheet\" href=\"{0}\" async />", s)).ToArray();

+ 17 - 2
MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

@@ -16,9 +16,10 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Text;
+using System.Text.RegularExpressions;
 using System.Threading;
 using System.Xml;
-
+using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Xml;
@@ -353,7 +354,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
 
                 if (!string.IsNullOrEmpty(stream.Language))
                 {
-                    writer.WriteElementString("language", stream.Language);
+                    // https://emby.media/community/index.php?/topic/49071-nfo-not-generated-on-actualize-or-rescan-or-identify
+                    writer.WriteElementString("language", RemoveInvalidXMLChars(stream.Language));
                 }
 
                 var scanType = stream.IsInterlaced ? "interlaced" : "progressive";
@@ -422,6 +424,19 @@ namespace MediaBrowser.XbmcMetadata.Savers
             writer.WriteEndElement();
         }
 
+        // filters control characters but allows only properly-formed surrogate sequences
+        private static Regex _invalidXMLChars = new Regex(
+            @"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]");
+
+        /// <summary>
+        /// removes any unusual unicode characters that can't be encoded into XML
+        /// </summary>
+        public static string RemoveInvalidXMLChars(string text)
+        {
+            if (string.IsNullOrEmpty(text)) return string.Empty;
+            return _invalidXMLChars.Replace(text, string.Empty);
+        }
+
         public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
 
         /// <summary>

+ 5 - 0
MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs

@@ -51,6 +51,11 @@ namespace MediaBrowser.XbmcMetadata.Savers
                 //}
 
                 list.Add(Path.ChangeExtension(item.Path, ".nfo"));
+
+                if (!item.IsInMixedFolder)
+                {
+                    list.Add(Path.Combine(item.ContainingFolderPath, "movie.nfo"));
+                }
             }
 
             return list;

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

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

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

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