diff options
| author | Luke <luke.pulverenti@gmail.com> | 2017-07-23 02:12:16 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-07-23 02:12:16 -0400 |
| commit | 5ec9d4e9fe4b3e5109ca1abf6c1ffdb87fd2d4dd (patch) | |
| tree | e49cc827583b6dffeabb142af5fae889b1a09581 | |
| parent | e99bc61d53f393dc475e265e3b5bc8c19b186594 (diff) | |
| parent | 0d1b5ad733e6f1bbf6d730e723969495dda99016 (diff) | |
Merge pull request #2767 from MediaBrowser/beta
Beta
69 files changed, 1006 insertions, 439 deletions
diff --git a/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs index 5bd18cb80..700d04c4d 100644 --- a/Emby.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/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; } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index e93ee5990..9345a1df7 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/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 } } diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 3344bfcfe..d2a160cf7 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/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; diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs index cf2c8d995..0516585ae 100644 --- a/Emby.Dlna/Eventing/EventManager.cs +++ b/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; } diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs index 3eb2bc1d5..d31dc155e 100644 --- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/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; } diff --git a/Emby.Dlna/Profiles/LgTvProfile.cs b/Emby.Dlna/Profiles/LgTvProfile.cs index 71f684ec4..f81fb1ee7 100644 --- a/Emby.Dlna/Profiles/LgTvProfile.cs +++ b/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 diff --git a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml index a61fefcc8..92e02799e 100644 --- a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml +++ b/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" /> diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs index 574d74958..ddc37da09 100644 --- a/Emby.Dlna/Service/BaseService.cs +++ b/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); } } } diff --git a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs index 98011ddd4..7be4101c8 100644 --- a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs +++ b/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(); } diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index df8d1ac45..f43e45441 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/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 diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 5486f404f..1b0cbb936 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/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. diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs index 3c8ad55fe..e266ea21b 100644 --- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs +++ b/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; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 28c23b766..05f78eba9 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/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); diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 6c37d5f7a..396bd8e88 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/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); } diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 4857008f3..c1360c887 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/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; diff --git a/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs b/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs index 428b6202b..143350a8b 100644 --- a/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs +++ b/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) { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 24afc03da..bf11b7d3a 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/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>(); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 0329ea606..919c0f10d 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/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); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 10b7132a1..4b4f61d53 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/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) + { + + } + } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 153f86aed..b6ba8b45c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/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) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 8bf7a052e..113cb33f4 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/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(); diff --git a/Emby.Server.Implementations/Localization/Ratings/es.txt b/Emby.Server.Implementations/Localization/Ratings/es.txt new file mode 100644 index 000000000..1ba24fb99 --- /dev/null +++ b/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
\ No newline at end of file diff --git a/Emby.Server.Implementations/Localization/Ratings/nz.txt b/Emby.Server.Implementations/Localization/Ratings/nz.txt index bc761dcab..46e4067ba 100644 --- a/Emby.Server.Implementations/Localization/Ratings/nz.txt +++ b/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
\ No newline at end of file +NZ-R,10 +NZ-MA,10
\ No newline at end of file diff --git a/Emby.Server.Implementations/ScheduledTasks/SystemUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/SystemUpdateTask.cs index a41e21a4b..0c9eb0f1b 100644 --- a/Emby.Server.Implementations/ScheduledTasks/SystemUpdateTask.cs +++ b/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> diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 717416da1..6e37c1dc1 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/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); diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 468bf925f..03336c936 100644 --- a/Emby.Server.Implementations/packages.config +++ b/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>
\ No newline at end of file diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs index 6e59cceec..fc8c0edf6 100644 --- a/MediaBrowser.Api/Dlna/DlnaServerService.cs +++ b/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; - } } } diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index c4cb0cb1d..9764a71db 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/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(); diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 221755916..f6c97e091 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/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) { diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 80d8c072e..3bb119cba 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/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); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 8d01e4021..c300fcce3 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/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; } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 63d2cd9eb..83157c703 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/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), diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index c6282bbad..6744fbd92 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/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, diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index e32970be5..9b3c8a08f 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/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 diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 6853ddbeb..536236f5f 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/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); diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index af8670eb1..44e096dd7 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/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); } } diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 5e21f6a84..a41b4cbf5 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/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()); } } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 2cf7d81af..eecc12432 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -80,7 +80,7 @@ namespace MediaBrowser.Api.Playback return 6; } - return 10; + return 6; } if (IsSegmentedLiveStream) diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 3b638208b..4bb3de882 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/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)] diff --git a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs index 86a974229..89e20b1b4 100644 --- a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs +++ b/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> diff --git a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs index ba20395d1..cf7b6ba6a 100644 --- a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs +++ b/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 }; diff --git a/MediaBrowser.Controller/Dlna/IEventManager.cs b/MediaBrowser.Controller/Dlna/IEventManager.cs index 54e2a02dd..8c91bd889 100644 --- a/MediaBrowser.Controller/Dlna/IEventManager.cs +++ b/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); } } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 7978f4761..c5d9b9203 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/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; } } diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs index b0881ba7c..4ec9adf4e 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs +++ b/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<MediaSourceInfo>.</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. diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index 3d1af2afa..6d220f3a3 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/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; diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index f590d9aec..11a9ceac4 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/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; } diff --git a/MediaBrowser.Controller/Sync/ISyncManager.cs b/MediaBrowser.Controller/Sync/ISyncManager.cs index fbcb549ad..291632ea5 100644 --- a/MediaBrowser.Controller/Sync/ISyncManager.cs +++ b/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> diff --git a/MediaBrowser.Controller/Sync/ISyncProvider.cs b/MediaBrowser.Controller/Sync/ISyncProvider.cs index ef34bfe69..aa4b36427 100644 --- a/MediaBrowser.Controller/Sync/ISyncProvider.cs +++ b/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 { - + } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index faf5b0667..f1bf29d92 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/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); diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index 0b6ecf385..df511b0da 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/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>(); diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index de6cbf4bb..48f9a4212 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/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 diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs index 025f1bc7a..dedbb428d 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs +++ b/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) diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs index 1843fb69a..df39f0556 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs +++ b/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; } } } diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index f1cbacd90..f58e605b2 100644 --- a/MediaBrowser.Model/Session/TranscodingInfo.cs +++ b/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 } }
\ No newline at end of file diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs index 6709426b8..f0e262c50 100644 --- a/MediaBrowser.Model/Sync/SyncJob.cs +++ b/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; } } } diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs index d4e580871..98fe766e0 100644 --- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs +++ b/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; } diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs index 235fd8c8a..c4ea106fb 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs +++ b/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) { } diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs index 2e549f610..618a4df45 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs +++ b/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; diff --git a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj index 3e69e9f12..196bdd096 100644 --- a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj +++ b/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>
diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index be928319f..df98c6584 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/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" /> diff --git a/MediaBrowser.Server.Mono/packages.config b/MediaBrowser.Server.Mono/packages.config index f1de00589..dcc477e9a 100644 --- a/MediaBrowser.Server.Mono/packages.config +++ b/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>
\ No newline at end of file diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 6b100b86d..bc38476ca 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/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; + } } } } diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 19f4921d3..591ac0fba 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/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" /> diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index ca382ffb3..0fa93db82 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/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>
\ No newline at end of file diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs index 7d825875d..da43dd63a 100644 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ b/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(); diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index cf7c25584..5015dfbf9 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/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> diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs index f32f89bc2..aea3f2c70 100644 --- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs +++ b/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; diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index ff58c13ac..26c19c79a 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/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> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index a5707a265..641db709a 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/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> |
