mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-03 19:17:24 -05:00 
			
		
		
		
	
						commit
						5ec9d4e9fe
					
				@ -736,10 +736,10 @@ namespace Emby.Common.Implementations.HttpClientManager
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                if (options.LogErrors)
 | 
					                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;
 | 
					                var response = webException.Response as HttpWebResponse;
 | 
				
			||||||
                if (response != null)
 | 
					                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;
 | 
					                return exception;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,7 @@ using System.Xml;
 | 
				
			|||||||
using MediaBrowser.Controller.Dto;
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
using MediaBrowser.Controller.Entities.Audio;
 | 
					using MediaBrowser.Controller.Entities.Audio;
 | 
				
			||||||
using MediaBrowser.Controller.MediaEncoding;
 | 
					using MediaBrowser.Controller.MediaEncoding;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Playlists;
 | 
				
			||||||
using MediaBrowser.Model.Globalization;
 | 
					using MediaBrowser.Model.Globalization;
 | 
				
			||||||
using MediaBrowser.Model.Xml;
 | 
					using MediaBrowser.Model.Xml;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -482,6 +483,12 @@ namespace Emby.Dlna.ContentDirectory
 | 
				
			|||||||
                return GetMusicArtistItems(item, null, user, sort, startIndex, limit);
 | 
					                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.HasValue)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (stubType.Value == StubType.People)
 | 
					                if (stubType.Value == StubType.People)
 | 
				
			||||||
@ -518,7 +525,7 @@ namespace Emby.Dlna.ContentDirectory
 | 
				
			|||||||
                StartIndex = startIndex,
 | 
					                StartIndex = startIndex,
 | 
				
			||||||
                User = user,
 | 
					                User = user,
 | 
				
			||||||
                IsMissing = false,
 | 
					                IsMissing = false,
 | 
				
			||||||
                PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Music },
 | 
					                PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows },
 | 
				
			||||||
                ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
 | 
					                ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
 | 
				
			||||||
                IsPlaceHolder = false,
 | 
					                IsPlaceHolder = false,
 | 
				
			||||||
                DtoOptions = GetDtoOptions()
 | 
					                DtoOptions = GetDtoOptions()
 | 
				
			||||||
@ -531,6 +538,278 @@ namespace Emby.Dlna.ContentDirectory
 | 
				
			|||||||
            return ToResult(queryResult);
 | 
					            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)
 | 
					        private QueryResult<ServerItem> GetMusicArtistItems(BaseItem item, Guid? parentId, User user, SortCriteria sort, int? startIndex, int? limit)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var query = new InternalItemsQuery(user)
 | 
					            var query = new InternalItemsQuery(user)
 | 
				
			||||||
@ -571,6 +850,19 @@ namespace Emby.Dlna.ContentDirectory
 | 
				
			|||||||
            return ToResult(result);
 | 
					            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)
 | 
					        private QueryResult<ServerItem> ToResult(QueryResult<BaseItem> result)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var serverItems = result
 | 
					            var serverItems = result
 | 
				
			||||||
@ -660,6 +952,56 @@ namespace Emby.Dlna.ContentDirectory
 | 
				
			|||||||
                stubType = StubType.People;
 | 
					                stubType = StubType.People;
 | 
				
			||||||
                id = id.Split(new[] { '_' }, 2)[1];
 | 
					                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))
 | 
					            if (Guid.TryParse(id, out itemId))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -696,6 +1038,16 @@ namespace Emby.Dlna.ContentDirectory
 | 
				
			|||||||
    public enum StubType
 | 
					    public enum StubType
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        Folder = 0,
 | 
					        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
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -399,6 +399,46 @@ namespace Emby.Dlna.Didl
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                return _localization.GetLocalizedString("HeaderPeople");
 | 
					                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 episode = item as Episode;
 | 
				
			||||||
            var season = context as Season;
 | 
					            var season = context as Season;
 | 
				
			||||||
 | 
				
			|||||||
@ -26,32 +26,34 @@ namespace Emby.Dlna.Eventing
 | 
				
			|||||||
            _logger = logger;
 | 
					            _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);
 | 
					            var subscription = GetSubscription(subscriptionId, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _logger.Debug("Renewing event subscription for {0} with timeout of {1} to {2}",
 | 
					            // Remove logging for now because some devices are sending this very frequently
 | 
				
			||||||
                subscription.NotificationType,
 | 
					            // TODO re-enable with dlna debug logging setting
 | 
				
			||||||
                timeout,
 | 
					            //_logger.Debug("Renewing event subscription for {0} with timeout of {1} to {2}",
 | 
				
			||||||
                subscription.CallbackUrl);
 | 
					            //    subscription.NotificationType,
 | 
				
			||||||
 | 
					            //    timeout,
 | 
				
			||||||
 | 
					            //    subscription.CallbackUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            subscription.TimeoutSeconds = timeout;
 | 
					            subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
 | 
				
			||||||
            subscription.SubscriptionTime = DateTime.UtcNow;
 | 
					            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");
 | 
					            var id = "uuid:" + Guid.NewGuid().ToString("N");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _logger.Debug("Creating event subscription for {0} with timeout of {1} to {2}",
 | 
					            // Remove logging for now because some devices are sending this very frequently
 | 
				
			||||||
                notificationType,
 | 
					            // TODO re-enable with dlna debug logging setting
 | 
				
			||||||
                timeout,
 | 
					            //_logger.Debug("Creating event subscription for {0} with timeout of {1} to {2}",
 | 
				
			||||||
                callbackUrl);
 | 
					            //    notificationType,
 | 
				
			||||||
 | 
					            //    timeout,
 | 
				
			||||||
 | 
					            //    callbackUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _subscriptions.TryAdd(id, new EventSubscription
 | 
					            _subscriptions.TryAdd(id, new EventSubscription
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -61,7 +63,25 @@ namespace Emby.Dlna.Eventing
 | 
				
			|||||||
                TimeoutSeconds = timeout
 | 
					                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)
 | 
					        public EventSubscriptionResponse CancelEventSubscription(string subscriptionId)
 | 
				
			||||||
@ -73,22 +93,22 @@ namespace Emby.Dlna.Eventing
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            return new EventSubscriptionResponse
 | 
					            return new EventSubscriptionResponse
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Content = "\r\n",
 | 
					                Content = string.Empty,
 | 
				
			||||||
                ContentType = "text/plain"
 | 
					                ContentType = "text/plain"
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 | 
					        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
 | 
					            var response = new EventSubscriptionResponse
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Content = "\r\n",
 | 
					                Content = string.Empty,
 | 
				
			||||||
                ContentType = "text/plain"
 | 
					                ContentType = "text/plain"
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            response.Headers["SID"] = subscriptionId;
 | 
					            response.Headers["SID"] = subscriptionId;
 | 
				
			||||||
            response.Headers["TIMEOUT"] = "SECOND-" + timeoutSeconds.ToString(_usCulture);
 | 
					            response.Headers["TIMEOUT"] = string.IsNullOrWhiteSpace(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return response;
 | 
					            return response;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -56,8 +56,9 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
            if (profile.Container.Length > 0)
 | 
					            if (profile.Container.Length > 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // Check container type
 | 
					                // Check container type
 | 
				
			||||||
                var mediaContainer = Path.GetExtension(mediaPath);
 | 
					                var mediaContainer = (Path.GetExtension(mediaPath) ?? string.Empty).TrimStart('.');
 | 
				
			||||||
                if (!profile.GetContainers().Any(i => string.Equals("." + i.TrimStart('.'), mediaContainer, StringComparison.OrdinalIgnoreCase)))
 | 
					
 | 
				
			||||||
 | 
					                if (!profile.SupportsContainer(mediaContainer))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return false;
 | 
					                    return false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -53,14 +53,7 @@ namespace Emby.Dlna.Profiles
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                new DirectPlayProfile
 | 
					                new DirectPlayProfile
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    Container = "ts",
 | 
					                    Container = "ts,mpegts,avi,mkv",
 | 
				
			||||||
                    VideoCodec = "h264",
 | 
					 | 
				
			||||||
                    AudioCodec = "aac,ac3,mp3,dca,dts",
 | 
					 | 
				
			||||||
                    Type = DlnaProfileType.Video
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                new DirectPlayProfile
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    Container = "mkv",
 | 
					 | 
				
			||||||
                    VideoCodec = "h264",
 | 
					                    VideoCodec = "h264",
 | 
				
			||||||
                    AudioCodec = "aac,ac3,mp3,dca,dts",
 | 
					                    AudioCodec = "aac,ac3,mp3,dca,dts",
 | 
				
			||||||
                    Type = DlnaProfileType.Video
 | 
					                    Type = DlnaProfileType.Video
 | 
				
			||||||
 | 
				
			|||||||
@ -35,8 +35,7 @@
 | 
				
			|||||||
  <IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
 | 
					  <IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
 | 
				
			||||||
  <XmlRootAttributes />
 | 
					  <XmlRootAttributes />
 | 
				
			||||||
  <DirectPlayProfiles>
 | 
					  <DirectPlayProfiles>
 | 
				
			||||||
    <DirectPlayProfile container="ts" 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="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="mp4,m4v" audioCodec="aac,ac3,mp3,dca,dts" videoCodec="h264,mpeg4" type="Video" />
 | 
				
			||||||
    <DirectPlayProfile container="mp3" type="Audio" />
 | 
					    <DirectPlayProfile container="mp3" type="Audio" />
 | 
				
			||||||
    <DirectPlayProfile container="jpeg" type="Photo" />
 | 
					    <DirectPlayProfile container="jpeg" type="Photo" />
 | 
				
			||||||
 | 
				
			|||||||
@ -24,14 +24,14 @@ namespace Emby.Dlna.Service
 | 
				
			|||||||
            return EventManager.CancelEventSubscription(subscriptionId);
 | 
					            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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Channels
 | 
				
			|||||||
            return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
 | 
					            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();
 | 
					            throw new NotImplementedException();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ using MediaBrowser.Model.Tasks;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Emby.Server.Implementations.Data
 | 
					namespace Emby.Server.Implementations.Data
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public class CleanDatabaseScheduledTask : IScheduledTask
 | 
					    public class CleanDatabaseScheduledTask : ILibraryPostScanTask
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly ILibraryManager _libraryManager;
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
        private readonly IItemRepository _itemRepo;
 | 
					        private readonly IItemRepository _itemRepo;
 | 
				
			||||||
@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Data
 | 
				
			|||||||
            get { return "Library"; }
 | 
					            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.
 | 
					            // Ensure these objects are lazy loaded.
 | 
				
			||||||
            // Without this there is a deadlock that will need to be investigated
 | 
					            // Without this there is a deadlock that will need to be investigated
 | 
				
			||||||
 | 
				
			|||||||
@ -307,7 +307,7 @@
 | 
				
			|||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
 | 
					    <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>
 | 
					      <Private>True</Private>
 | 
				
			||||||
    </Reference>
 | 
					    </Reference>
 | 
				
			||||||
    <Reference Include="System" />
 | 
					    <Reference Include="System" />
 | 
				
			||||||
@ -418,6 +418,9 @@
 | 
				
			|||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
    <EmbeddedResource Include="Localization\Ratings\uk.txt" />
 | 
					    <EmbeddedResource Include="Localization\Ratings\uk.txt" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
 | 
					  <ItemGroup>
 | 
				
			||||||
 | 
					    <EmbeddedResource Include="Localization\Ratings\es.txt" />
 | 
				
			||||||
 | 
					  </ItemGroup>
 | 
				
			||||||
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 | 
					  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 | 
				
			||||||
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
 | 
					  <!-- 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.
 | 
					       Other similar extension points exist, see Microsoft.Common.targets.
 | 
				
			||||||
 | 
				
			|||||||
@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.FFMpeg
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(customffProbePath))
 | 
					            if (!string.IsNullOrWhiteSpace(customffProbePath))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                info.EncoderPath = customffProbePath;
 | 
					                info.ProbePath = customffProbePath;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return info;
 | 
					            return info;
 | 
				
			||||||
 | 
				
			|||||||
@ -104,6 +104,7 @@ namespace Emby.Server.Implementations.HttpServer
 | 
				
			|||||||
        readonly Dictionary<Type, int> _mapExceptionToStatusCode = new Dictionary<Type, int>
 | 
					        readonly Dictionary<Type, int> _mapExceptionToStatusCode = new Dictionary<Type, int>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                {typeof (ResourceNotFoundException), 404},
 | 
					                {typeof (ResourceNotFoundException), 404},
 | 
				
			||||||
 | 
					                {typeof (RemoteServiceUnavailableException), 502},
 | 
				
			||||||
                {typeof (FileNotFoundException), 404},
 | 
					                {typeof (FileNotFoundException), 404},
 | 
				
			||||||
                //{typeof (DirectoryNotFoundException), 404},
 | 
					                //{typeof (DirectoryNotFoundException), 404},
 | 
				
			||||||
                {typeof (SecurityException), 401},
 | 
					                {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)
 | 
					        private int GetStatusCode(Exception ex)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (ex is ArgumentException)
 | 
					            if (ex is ArgumentException)
 | 
				
			||||||
@ -280,7 +304,7 @@ namespace Emby.Server.Implementations.HttpServer
 | 
				
			|||||||
            int statusCode;
 | 
					            int statusCode;
 | 
				
			||||||
            if (!_mapExceptionToStatusCode.TryGetValue(exceptionType, out statusCode))
 | 
					            if (!_mapExceptionToStatusCode.TryGetValue(exceptionType, out statusCode))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (string.Equals(exceptionType.Name, "DirectoryNotFoundException", StringComparison.OrdinalIgnoreCase))
 | 
					                if (ex is DirectoryNotFoundException)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    statusCode = 404;
 | 
					                    statusCode = 404;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -297,6 +321,8 @@ namespace Emby.Server.Implementations.HttpServer
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                ex = GetActualException(ex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (logException)
 | 
					                if (logException)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _logger.ErrorException("Error processing request", ex);
 | 
					                    _logger.ErrorException("Error processing request", ex);
 | 
				
			||||||
 | 
				
			|||||||
@ -438,6 +438,15 @@ namespace Emby.Server.Implementations.HttpServer
 | 
				
			|||||||
            options.CacheKey = cacheKey.GetMD5();
 | 
					            options.CacheKey = cacheKey.GetMD5();
 | 
				
			||||||
            options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare));
 | 
					            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);
 | 
					            return GetStaticResult(requestContext, options);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -371,7 +371,7 @@ namespace Emby.Server.Implementations.Library
 | 
				
			|||||||
                var tuple = GetProvider(request.OpenToken);
 | 
					                var tuple = GetProvider(request.OpenToken);
 | 
				
			||||||
                var provider = tuple.Item1;
 | 
					                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;
 | 
					                var mediaSource = mediaSourceTuple.Item1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -16,8 +16,8 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
        private readonly IMediaEncoder _mediaEncoder;
 | 
					        private readonly IMediaEncoder _mediaEncoder;
 | 
				
			||||||
        private readonly ILogger _logger;
 | 
					        private readonly ILogger _logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const int ProbeAnalyzeDurationMs = 2000;
 | 
					        const int ProbeAnalyzeDurationMs = 3000;
 | 
				
			||||||
        const int PlaybackAnalyzeDurationMs = 2000;
 | 
					        const int PlaybackAnalyzeDurationMs = 3000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger)
 | 
					        public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
				
			|||||||
@ -1514,7 +1514,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private DateTime _lastRecordingRefreshTime;
 | 
					        private DateTime _lastRecordingRefreshTime;
 | 
				
			||||||
        private async Task RefreshRecordings(CancellationToken cancellationToken)
 | 
					        private async Task RefreshRecordings(Guid internalLiveTvFolderId, CancellationToken cancellationToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            const int cacheMinutes = 2;
 | 
					            const int cacheMinutes = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1542,10 +1542,8 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 | 
					                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);
 | 
					                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)
 | 
					            if (user == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -1571,21 +1569,31 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
                return new QueryResult<BaseItem>();
 | 
					                return new QueryResult<BaseItem>();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders()
 | 
					            var folderIds = EmbyTV.EmbyTV.Current.GetRecordingFolders()
 | 
				
			||||||
                .SelectMany(i => i.Locations)
 | 
					                .SelectMany(i => i.Locations)
 | 
				
			||||||
                .Distinct(StringComparer.OrdinalIgnoreCase)
 | 
					                .Distinct(StringComparer.OrdinalIgnoreCase)
 | 
				
			||||||
                .Select(i => _libraryManager.FindByPath(i, true))
 | 
					                .Select(i => _libraryManager.FindByPath(i, true))
 | 
				
			||||||
                .Where(i => i != null)
 | 
					                .Where(i => i != null)
 | 
				
			||||||
                .Where(i => i.IsVisibleStandalone(user))
 | 
					                .Where(i => i.IsVisibleStandalone(user))
 | 
				
			||||||
 | 
					                .Select(i => i.Id)
 | 
				
			||||||
                .ToList();
 | 
					                .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>();
 | 
					                return new QueryResult<BaseItem>();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var includeItemTypes = new List<string>();
 | 
					            var includeItemTypes = new List<string>();
 | 
				
			||||||
            var excludeItemTypes = new List<string>();
 | 
					 | 
				
			||||||
            var genres = new List<string>();
 | 
					            var genres = new List<string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (query.IsMovie.HasValue)
 | 
					            if (query.IsMovie.HasValue)
 | 
				
			||||||
@ -1631,7 +1639,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                MediaTypes = new[] { MediaType.Video },
 | 
					                MediaTypes = new[] { MediaType.Video },
 | 
				
			||||||
                Recursive = true,
 | 
					                Recursive = true,
 | 
				
			||||||
                AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
 | 
					                AncestorIds = folderIds.Select(i => i.ToString("N")).ToArray(),
 | 
				
			||||||
                IsFolder = false,
 | 
					                IsFolder = false,
 | 
				
			||||||
                IsVirtualItem = false,
 | 
					                IsVirtualItem = false,
 | 
				
			||||||
                Limit = query.Limit,
 | 
					                Limit = query.Limit,
 | 
				
			||||||
@ -1714,12 +1722,24 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
                return new QueryResult<BaseItem>();
 | 
					                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);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await RefreshRecordings(cancellationToken).ConfigureAwait(false);
 | 
					                return GetEmbyRecordings(query, options, folder.Id, user);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            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)
 | 
					            var internalQuery = new InternalItemsQuery(user)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -2620,7 +2640,8 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            }, new DtoOptions(), cancellationToken).ConfigureAwait(false);
 | 
					            }, 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>();
 | 
					            var groups = new List<BaseItemDto>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -118,7 +118,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
            return list;
 | 
					            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;
 | 
					            MediaSourceInfo stream = null;
 | 
				
			||||||
            const bool isAudio = false;
 | 
					            const bool isAudio = false;
 | 
				
			||||||
@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            try
 | 
					            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);
 | 
					                    AddMediaInfo(stream, isAudio, cancellationToken);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -10,9 +10,11 @@ using System.IO;
 | 
				
			|||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using MediaBrowser.Common.Extensions;
 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					using MediaBrowser.Controller.Configuration;
 | 
				
			||||||
using MediaBrowser.Controller.MediaEncoding;
 | 
					using MediaBrowser.Controller.MediaEncoding;
 | 
				
			||||||
using MediaBrowser.Model.Dlna;
 | 
					using MediaBrowser.Model.Dlna;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.IO;
 | 
				
			||||||
using MediaBrowser.Model.Serialization;
 | 
					using MediaBrowser.Model.Serialization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts
 | 
					namespace Emby.Server.Implementations.LiveTv.TunerHosts
 | 
				
			||||||
@ -23,16 +25,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 | 
				
			|||||||
        protected readonly ILogger Logger;
 | 
					        protected readonly ILogger Logger;
 | 
				
			||||||
        protected IJsonSerializer JsonSerializer;
 | 
					        protected IJsonSerializer JsonSerializer;
 | 
				
			||||||
        protected readonly IMediaEncoder MediaEncoder;
 | 
					        protected readonly IMediaEncoder MediaEncoder;
 | 
				
			||||||
 | 
					        protected readonly IFileSystem FileSystem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
 | 
					        private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
 | 
				
			||||||
            new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
 | 
					            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;
 | 
					            Config = config;
 | 
				
			||||||
            Logger = logger;
 | 
					            Logger = logger;
 | 
				
			||||||
            JsonSerializer = jsonSerializer;
 | 
					            JsonSerializer = jsonSerializer;
 | 
				
			||||||
            MediaEncoder = mediaEncoder;
 | 
					            MediaEncoder = mediaEncoder;
 | 
				
			||||||
 | 
					            FileSystem = fileSystem;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected abstract Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken);
 | 
					        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)
 | 
					            foreach (var host in hosts)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                var channelCacheFile = Path.Combine(Config.ApplicationPaths.CachePath, host.Id + "_channels");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                try
 | 
					                try
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    var channels = await GetChannels(host, enableCache, cancellationToken).ConfigureAwait(false);
 | 
					                    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();
 | 
					                    var newChannels = channels.Where(i => !list.Any(l => string.Equals(i.Id, l.Id, StringComparison.OrdinalIgnoreCase))).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    list.AddRange(newChannels);
 | 
					                    list.AddRange(newChannels);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (!enableCache)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        try
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            FileSystem.CreateDirectory(FileSystem.GetDirectoryName(channelCacheFile));
 | 
				
			||||||
 | 
					                            JsonSerializer.SerializeToFile(channels, channelCacheFile);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        catch (IOException)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                catch (Exception ex)
 | 
					                catch (Exception ex)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    Logger.ErrorException("Error getting channel list", ex);
 | 
					                    Logger.ErrorException("Error getting channel list", ex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (enableCache)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        try
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            var channels = JsonSerializer.DeserializeFromFile<List<ChannelInfo>>(channelCacheFile);
 | 
				
			||||||
 | 
					                            list.AddRange(channels);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        catch (IOException)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -27,17 +27,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
				
			|||||||
    public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
 | 
					    public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IHttpClient _httpClient;
 | 
					        private readonly IHttpClient _httpClient;
 | 
				
			||||||
        private readonly IFileSystem _fileSystem;
 | 
					 | 
				
			||||||
        private readonly IServerApplicationHost _appHost;
 | 
					        private readonly IServerApplicationHost _appHost;
 | 
				
			||||||
        private readonly ISocketFactory _socketFactory;
 | 
					        private readonly ISocketFactory _socketFactory;
 | 
				
			||||||
        private readonly INetworkManager _networkManager;
 | 
					        private readonly INetworkManager _networkManager;
 | 
				
			||||||
        private readonly IEnvironmentInfo _environment;
 | 
					        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)
 | 
					        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)
 | 
				
			||||||
            : base(config, logger, jsonSerializer, mediaEncoder)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _httpClient = httpClient;
 | 
					            _httpClient = httpClient;
 | 
				
			||||||
            _fileSystem = fileSystem;
 | 
					 | 
				
			||||||
            _appHost = appHost;
 | 
					            _appHost = appHost;
 | 
				
			||||||
            _socketFactory = socketFactory;
 | 
					            _socketFactory = socketFactory;
 | 
				
			||||||
            _networkManager = networkManager;
 | 
					            _networkManager = networkManager;
 | 
				
			||||||
@ -509,7 +506,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
 | 
					            if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
 | 
					                return new HdHomerunUdpStream(mediaSource, 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
 | 
					            // 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;
 | 
					                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)
 | 
					        public async Task Validate(TunerHostInfo info)
 | 
				
			||||||
 | 
				
			|||||||
@ -25,15 +25,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
 | 
					    public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IFileSystem _fileSystem;
 | 
					 | 
				
			||||||
        private readonly IHttpClient _httpClient;
 | 
					        private readonly IHttpClient _httpClient;
 | 
				
			||||||
        private readonly IServerApplicationHost _appHost;
 | 
					        private readonly IServerApplicationHost _appHost;
 | 
				
			||||||
        private readonly IEnvironmentInfo _environment;
 | 
					        private readonly IEnvironmentInfo _environment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, 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, fileSystem)
 | 
				
			||||||
            : base(config, logger, jsonSerializer, mediaEncoder)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _fileSystem = fileSystem;
 | 
					 | 
				
			||||||
            _httpClient = httpClient;
 | 
					            _httpClient = httpClient;
 | 
				
			||||||
            _appHost = appHost;
 | 
					            _appHost = appHost;
 | 
				
			||||||
            _environment = environment;
 | 
					            _environment = environment;
 | 
				
			||||||
@ -51,7 +48,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
 | 
					        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();
 | 
					            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 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;
 | 
					            return liveStream;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task Validate(TunerHostInfo info)
 | 
					        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"),
 | 
					                    Id = channel.Path.GetMD5().ToString("N"),
 | 
				
			||||||
                    IsInfiniteStream = true,
 | 
					                    IsInfiniteStream = true,
 | 
				
			||||||
                    IsRemote = true
 | 
					                    IsRemote = true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    IgnoreDts = true
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                mediaSource.InferTotalBitrate();
 | 
					                mediaSource.InferTotalBitrate();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								Emby.Server.Implementations/Localization/Ratings/es.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								Emby.Server.Implementations/Localization/Ratings/es.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					ES-A,1
 | 
				
			||||||
 | 
					ES-7,3
 | 
				
			||||||
 | 
					ES-12,6
 | 
				
			||||||
 | 
					ES-16,8
 | 
				
			||||||
 | 
					ES-18,11
 | 
				
			||||||
@ -1,10 +1,11 @@
 | 
				
			|||||||
NZ-G,1
 | 
					NZ-G,1
 | 
				
			||||||
NZ-PG,5
 | 
					NZ-PG,5
 | 
				
			||||||
NZ-M,9
 | 
					NZ-M,6
 | 
				
			||||||
NZ-R13,7
 | 
					NZ-R13,7
 | 
				
			||||||
 | 
					NZ-RP13,7
 | 
				
			||||||
NZ-R15,8
 | 
					NZ-R15,8
 | 
				
			||||||
 | 
					NZ-RP16,9
 | 
				
			||||||
NZ-R16,9
 | 
					NZ-R16,9
 | 
				
			||||||
NZ-R18,10
 | 
					NZ-R18,10
 | 
				
			||||||
NZ-RP13,7
 | 
					 | 
				
			||||||
NZ-RP16,9
 | 
					 | 
				
			||||||
NZ-R,10
 | 
					NZ-R,10
 | 
				
			||||||
 | 
					NZ-MA,10
 | 
				
			||||||
@ -68,23 +68,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
 | 
				
			|||||||
        /// <returns>Task.</returns>
 | 
					        /// <returns>Task.</returns>
 | 
				
			||||||
        public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
 | 
					        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
 | 
					            // Create a progress object for the update check
 | 
				
			||||||
            var innerProgress = new SimpleProgress<double>();
 | 
					            var updateInfo = await _appHost.CheckForApplicationUpdate(cancellationToken, new SimpleProgress<double>()).ConfigureAwait(false);
 | 
				
			||||||
            innerProgress.ProgressChanged += innerProgressHandler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var updateInfo = await _appHost.CheckForApplicationUpdate(cancellationToken, innerProgress).ConfigureAwait(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Release the event handler
 | 
					 | 
				
			||||||
            innerProgress.ProgressChanged -= innerProgressHandler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            progress.Report(10);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!updateInfo.IsUpdateAvailable)
 | 
					            if (!updateInfo.IsUpdateAvailable)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Logger.Debug("No application update available.");
 | 
					                Logger.Debug("No application update available.");
 | 
				
			||||||
                progress.Report(100);
 | 
					 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -96,22 +85,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                Logger.Info("Update Revision {0} available.  Updating...", updateInfo.AvailableVersion);
 | 
					                Logger.Info("Update Revision {0} available.  Updating...", updateInfo.AvailableVersion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                innerProgressHandler = (sender, e) => progress.Report(e * .9 + .1);
 | 
					                await _appHost.UpdateApplication(updateInfo.Package, cancellationToken, progress).ConfigureAwait(false);
 | 
				
			||||||
 | 
					 | 
				
			||||||
                innerProgress = new SimpleProgress<double>();
 | 
					 | 
				
			||||||
                innerProgress.ProgressChanged += innerProgressHandler;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                await _appHost.UpdateApplication(updateInfo.Package, cancellationToken, innerProgress).ConfigureAwait(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Release the event handler
 | 
					 | 
				
			||||||
                innerProgress.ProgressChanged -= innerProgressHandler;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Logger.Info("A new version of " + _appHost.Name + " is available.");
 | 
					                Logger.Info("A new version of " + _appHost.Name + " is available.");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
            progress.Report(100);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
 | 
				
			|||||||
@ -513,8 +513,6 @@ namespace Emby.Server.Implementations.Updates
 | 
				
			|||||||
                    CurrentInstallations.Remove(tuple);
 | 
					                    CurrentInstallations.Remove(tuple);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                progress.Report(100);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                CompletedInstallationsInternal.Add(installationInfo);
 | 
					                CompletedInstallationsInternal.Add(installationInfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                EventHelper.FireEventIfNotNull(PackageInstallationCompleted, this, installationEventArgs, _logger);
 | 
					                EventHelper.FireEventIfNotNull(PackageInstallationCompleted, this, installationEventArgs, _logger);
 | 
				
			||||||
 | 
				
			|||||||
@ -3,5 +3,5 @@
 | 
				
			|||||||
  <package id="Emby.XmlTv" version="1.0.9" targetFramework="net46" />
 | 
					  <package id="Emby.XmlTv" version="1.0.9" targetFramework="net46" />
 | 
				
			||||||
  <package id="MediaBrowser.Naming" version="1.0.5" targetFramework="portable45-net45+win8" />
 | 
					  <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="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>
 | 
					</packages>
 | 
				
			||||||
@ -219,20 +219,20 @@ namespace MediaBrowser.Api.Dlna
 | 
				
			|||||||
        private object ProcessEventRequest(IEventManager eventManager)
 | 
					        private object ProcessEventRequest(IEventManager eventManager)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var subscriptionId = GetHeader("SID");
 | 
					            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))
 | 
					            if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                var notificationType = GetHeader("NT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var callback = GetHeader("CALLBACK");
 | 
				
			||||||
 | 
					                var timeoutString = GetHeader("TIMEOUT");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (string.IsNullOrEmpty(notificationType))
 | 
					                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));
 | 
					            return GetSubscriptionResponse(eventManager.CancelEventSubscription(subscriptionId));
 | 
				
			||||||
@ -242,24 +242,5 @@ namespace MediaBrowser.Api.Dlna
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            return ResultFactory.GetResult(response.Content, response.ContentType, response.Headers);
 | 
					            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;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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")]
 | 
					    [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")]
 | 
				
			||||||
    public class GetNetworkShares : IReturn<List<FileSystemEntryInfo>>
 | 
					    public class GetNetworkShares : IReturn<List<FileSystemEntryInfo>>
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -112,7 +126,7 @@ namespace MediaBrowser.Api
 | 
				
			|||||||
        /// The _network manager
 | 
					        /// The _network manager
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        private readonly INetworkManager _networkManager;
 | 
					        private readonly INetworkManager _networkManager;
 | 
				
			||||||
        private IFileSystem _fileSystem;
 | 
					        private readonly IFileSystem _fileSystem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Initializes a new instance of the <see cref="EnvironmentService" /> class.
 | 
					        /// Initializes a new instance of the <see cref="EnvironmentService" /> class.
 | 
				
			||||||
@ -129,6 +143,48 @@ namespace MediaBrowser.Api
 | 
				
			|||||||
            _fileSystem = fileSystem;
 | 
					            _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)
 | 
					        public object Get(GetDefaultDirectoryBrowser request)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var result = new DefaultDirectoryBrowserInfo();
 | 
					            var result = new DefaultDirectoryBrowserInfo();
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@ using System.Threading.Tasks;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using MediaBrowser.Controller.Dto;
 | 
					using MediaBrowser.Controller.Dto;
 | 
				
			||||||
using MediaBrowser.Controller.IO;
 | 
					using MediaBrowser.Controller.IO;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.LiveTv;
 | 
				
			||||||
using MediaBrowser.Model.IO;
 | 
					using MediaBrowser.Model.IO;
 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					using MediaBrowser.Model.Services;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -567,7 +568,9 @@ namespace MediaBrowser.Api.Images
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            }).ToList() : new List<IImageEnhancer>();
 | 
					            }).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)
 | 
					            if (request.CropWhitespace.HasValue)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
				
			|||||||
@ -512,10 +512,6 @@ namespace MediaBrowser.Api.Library
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var headers = new Dictionary<string, string>();
 | 
					            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)
 | 
					            if (user != null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                LogDownload(item, user, auth);
 | 
					                LogDownload(item, user, auth);
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,7 @@ using System.Threading.Tasks;
 | 
				
			|||||||
using MediaBrowser.Common.Net;
 | 
					using MediaBrowser.Common.Net;
 | 
				
			||||||
using MediaBrowser.Controller;
 | 
					using MediaBrowser.Controller;
 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					using MediaBrowser.Controller.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Configuration;
 | 
				
			||||||
using MediaBrowser.Model.Diagnostics;
 | 
					using MediaBrowser.Model.Diagnostics;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MediaBrowser.Api.Playback
 | 
					namespace MediaBrowser.Api.Playback
 | 
				
			||||||
@ -100,11 +101,7 @@ namespace MediaBrowser.Api.Playback
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the command line arguments.
 | 
					        /// Gets the command line arguments.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="outputPath">The output path.</param>
 | 
					        protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding);
 | 
				
			||||||
        /// <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);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the type of the transcoding job.
 | 
					        /// Gets the type of the transcoding job.
 | 
				
			||||||
@ -125,11 +122,11 @@ namespace MediaBrowser.Api.Playback
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the output file path.
 | 
					        /// Gets the output file path.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        private string GetOutputFilePath(StreamState state, string outputFileExtension)
 | 
					        private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
 | 
					            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.DeviceId ?? string.Empty);
 | 
				
			||||||
            data += "-" + (state.Request.PlaySessionId ?? 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 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
 | 
					            var process = ApiEntryPoint.Instance.ProcessFactory.Create(new ProcessOptions
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -826,7 +825,10 @@ namespace MediaBrowser.Api.Playback
 | 
				
			|||||||
            var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
 | 
					            var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
 | 
				
			||||||
                ? GetOutputFileExtension(state)
 | 
					                ? GetOutputFileExtension(state)
 | 
				
			||||||
                : ("." + state.OutputContainer);
 | 
					                : ("." + state.OutputContainer);
 | 
				
			||||||
            state.OutputFilePath = GetOutputFilePath(state, ext);
 | 
					
 | 
				
			||||||
 | 
					            var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            state.OutputFilePath = GetOutputFilePath(state, encodingOptions, ext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return state;
 | 
					            return state;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@ using System.Text;
 | 
				
			|||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					using MediaBrowser.Controller.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Configuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MediaBrowser.Api.Playback.Hls
 | 
					namespace MediaBrowser.Api.Playback.Hls
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -25,15 +26,12 @@ namespace MediaBrowser.Api.Playback.Hls
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the audio arguments.
 | 
					        /// Gets the audio arguments.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="state">The state.</param>
 | 
					        protected abstract string GetAudioArguments(StreamState state, EncodingOptions encodingOptions);
 | 
				
			||||||
        /// <returns>System.String.</returns>
 | 
					
 | 
				
			||||||
        protected abstract string GetAudioArguments(StreamState state);
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the video arguments.
 | 
					        /// Gets the video arguments.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="state">The state.</param>
 | 
					        protected abstract string GetVideoArguments(StreamState state, EncodingOptions encodingOptions);
 | 
				
			||||||
        /// <returns>System.String.</returns>
 | 
					 | 
				
			||||||
        protected abstract string GetVideoArguments(StreamState state);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the segment file extension.
 | 
					        /// 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 itsOffsetMs = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
 | 
					            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),
 | 
					                    EncodingHelper.GetInputArgument(state, encodingOptions),
 | 
				
			||||||
                    threads,
 | 
					                    threads,
 | 
				
			||||||
                    EncodingHelper.GetMapArgs(state),
 | 
					                    EncodingHelper.GetMapArgs(state),
 | 
				
			||||||
                    GetVideoArguments(state),
 | 
					                    GetVideoArguments(state, encodingOptions),
 | 
				
			||||||
                    GetAudioArguments(state),
 | 
					                    GetAudioArguments(state, encodingOptions),
 | 
				
			||||||
                    state.SegmentLength.ToString(UsCulture),
 | 
					                    state.SegmentLength.ToString(UsCulture),
 | 
				
			||||||
                    startNumberParam,
 | 
					                    startNumberParam,
 | 
				
			||||||
                    outputPath,
 | 
					                    outputPath,
 | 
				
			||||||
@ -306,8 +302,8 @@ namespace MediaBrowser.Api.Playback.Hls
 | 
				
			|||||||
                EncodingHelper.GetInputArgument(state, encodingOptions),
 | 
					                EncodingHelper.GetInputArgument(state, encodingOptions),
 | 
				
			||||||
                threads,
 | 
					                threads,
 | 
				
			||||||
                EncodingHelper.GetMapArgs(state),
 | 
					                EncodingHelper.GetMapArgs(state),
 | 
				
			||||||
                GetVideoArguments(state),
 | 
					                GetVideoArguments(state, encodingOptions),
 | 
				
			||||||
                GetAudioArguments(state),
 | 
					                GetAudioArguments(state, encodingOptions),
 | 
				
			||||||
                state.SegmentLength.ToString(UsCulture),
 | 
					                state.SegmentLength.ToString(UsCulture),
 | 
				
			||||||
                startNumberParam,
 | 
					                startNumberParam,
 | 
				
			||||||
                state.HlsListSize.ToString(UsCulture),
 | 
					                state.HlsListSize.ToString(UsCulture),
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@ using System.Linq;
 | 
				
			|||||||
using System.Text;
 | 
					using System.Text;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Configuration;
 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					using MediaBrowser.Model.Services;
 | 
				
			||||||
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
 | 
					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>());
 | 
					            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 (!state.IsOutputVideo)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
 | 
					                if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return "-acodec copy";
 | 
					                    return "-acodec copy";
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                var audioTranscodeParams = new List<string>();
 | 
					                var audioTranscodeParams = new List<string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                audioTranscodeParams.Add("-acodec " + codec);
 | 
					                audioTranscodeParams.Add("-acodec " + audioCodec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (state.OutputAudioBitrate.HasValue)
 | 
					                if (state.OutputAudioBitrate.HasValue)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@ -811,12 +812,19 @@ namespace MediaBrowser.Api.Playback.Hls
 | 
				
			|||||||
                return string.Join(" ", audioTranscodeParams.ToArray());
 | 
					                return string.Join(" ", audioTranscodeParams.ToArray());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
 | 
					            if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                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 -copypriorss:a:0 0";
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var args = "-codec:a:0 " + codec;
 | 
					                return "-codec:a:0 copy";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var args = "-codec:a:0 " + audioCodec;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var channels = state.OutputAudioChannels;
 | 
					            var channels = state.OutputAudioChannels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -837,19 +845,19 @@ namespace MediaBrowser.Api.Playback.Hls
 | 
				
			|||||||
                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture);
 | 
					                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), true);
 | 
					            args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return args;
 | 
					            return args;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected override string GetVideoArguments(StreamState state)
 | 
					        protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!state.IsOutputVideo)
 | 
					            if (!state.IsOutputVideo)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return string.Empty;
 | 
					                return string.Empty;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var codec = EncodingHelper.GetVideoEncoder(state, ApiEntryPoint.Instance.GetEncodingOptions());
 | 
					            var codec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var args = "-codec:v:0 " + codec;
 | 
					            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 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 += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
 | 
					                //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;
 | 
					            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 threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
 | 
					            var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
 | 
				
			||||||
@ -940,7 +945,7 @@ namespace MediaBrowser.Api.Playback.Hls
 | 
				
			|||||||
                segmentFormat = "mpegts";
 | 
					                segmentFormat = "mpegts";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var videoCodec = EncodingHelper.GetVideoEncoder(state, ApiEntryPoint.Instance.GetEncodingOptions());
 | 
					            var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions);
 | 
				
			||||||
            var breakOnNonKeyFrames = state.EnableBreakOnNonKeyFrames(videoCodec);
 | 
					            var breakOnNonKeyFrames = state.EnableBreakOnNonKeyFrames(videoCodec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var breakOnNonKeyFramesArg = breakOnNonKeyFrames ? " -break_non_keyframes 1" : "";
 | 
					            var breakOnNonKeyFramesArg = breakOnNonKeyFrames ? " -break_non_keyframes 1" : "";
 | 
				
			||||||
@ -950,8 +955,8 @@ namespace MediaBrowser.Api.Playback.Hls
 | 
				
			|||||||
                EncodingHelper.GetInputArgument(state, encodingOptions),
 | 
					                EncodingHelper.GetInputArgument(state, encodingOptions),
 | 
				
			||||||
                threads,
 | 
					                threads,
 | 
				
			||||||
                mapArgs,
 | 
					                mapArgs,
 | 
				
			||||||
                GetVideoArguments(state),
 | 
					                GetVideoArguments(state, encodingOptions),
 | 
				
			||||||
                GetAudioArguments(state),
 | 
					                GetAudioArguments(state, encodingOptions),
 | 
				
			||||||
                state.SegmentLength.ToString(UsCulture),
 | 
					                state.SegmentLength.ToString(UsCulture),
 | 
				
			||||||
                startNumberParam,
 | 
					                startNumberParam,
 | 
				
			||||||
                outputPath,
 | 
					                outputPath,
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ using MediaBrowser.Model.IO;
 | 
				
			|||||||
using MediaBrowser.Model.Serialization;
 | 
					using MediaBrowser.Model.Serialization;
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					using MediaBrowser.Controller.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Configuration;
 | 
				
			||||||
using MediaBrowser.Model.Dlna;
 | 
					using MediaBrowser.Model.Dlna;
 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					using MediaBrowser.Model.Services;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -31,9 +32,7 @@ namespace MediaBrowser.Api.Playback.Hls
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the audio arguments.
 | 
					        /// Gets the audio arguments.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="state">The state.</param>
 | 
					        protected override string GetAudioArguments(StreamState state, EncodingOptions encodingOptions)
 | 
				
			||||||
        /// <returns>System.String.</returns>
 | 
					 | 
				
			||||||
        protected override string GetAudioArguments(StreamState state)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var codec = EncodingHelper.GetAudioEncoder(state);
 | 
					            var codec = EncodingHelper.GetAudioEncoder(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -63,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls
 | 
				
			|||||||
                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture);
 | 
					                args += " -ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            args += " " + EncodingHelper.GetAudioFilterParam(state, ApiEntryPoint.Instance.GetEncodingOptions(), true);
 | 
					            args += " " + EncodingHelper.GetAudioFilterParam(state, encodingOptions, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return args;
 | 
					            return args;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -71,11 +70,14 @@ namespace MediaBrowser.Api.Playback.Hls
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the video arguments.
 | 
					        /// Gets the video arguments.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="state">The state.</param>
 | 
					        protected override string GetVideoArguments(StreamState state, EncodingOptions encodingOptions)
 | 
				
			||||||
        /// <returns>System.String.</returns>
 | 
					 | 
				
			||||||
        protected override string GetVideoArguments(StreamState state)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            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;
 | 
					            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 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 += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Add resolution params, if specified
 | 
					                // Add resolution params, if specified
 | 
				
			||||||
 | 
				
			|||||||
@ -134,7 +134,7 @@ namespace MediaBrowser.Api.Playback
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
 | 
					                SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
 | 
				
			||||||
                    request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
 | 
					                    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
 | 
					            else
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -176,7 +176,7 @@ namespace MediaBrowser.Api.Playback
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                var mediaSourceId = request.MediaSourceId;
 | 
					                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)
 | 
					            if (request.AutoOpenLiveStream)
 | 
				
			||||||
@ -191,7 +191,6 @@ namespace MediaBrowser.Api.Playback
 | 
				
			|||||||
                        DeviceProfile = request.DeviceProfile,
 | 
					                        DeviceProfile = request.DeviceProfile,
 | 
				
			||||||
                        EnableDirectPlay = request.EnableDirectPlay,
 | 
					                        EnableDirectPlay = request.EnableDirectPlay,
 | 
				
			||||||
                        EnableDirectStream = request.EnableDirectStream,
 | 
					                        EnableDirectStream = request.EnableDirectStream,
 | 
				
			||||||
                        ForceDirectPlayRemoteMediaSource = request.ForceDirectPlayRemoteMediaSource,
 | 
					 | 
				
			||||||
                        ItemId = request.Id,
 | 
					                        ItemId = request.Id,
 | 
				
			||||||
                        MaxAudioChannels = request.MaxAudioChannels,
 | 
					                        MaxAudioChannels = request.MaxAudioChannels,
 | 
				
			||||||
                        MaxStreamingBitrate = request.MaxStreamingBitrate,
 | 
					                        MaxStreamingBitrate = request.MaxStreamingBitrate,
 | 
				
			||||||
@ -199,7 +198,8 @@ namespace MediaBrowser.Api.Playback
 | 
				
			|||||||
                        StartTimeTicks = request.StartTimeTicks,
 | 
					                        StartTimeTicks = request.StartTimeTicks,
 | 
				
			||||||
                        SubtitleStreamIndex = request.SubtitleStreamIndex,
 | 
					                        SubtitleStreamIndex = request.SubtitleStreamIndex,
 | 
				
			||||||
                        UserId = request.UserId,
 | 
					                        UserId = request.UserId,
 | 
				
			||||||
                        OpenToken = mediaSource.OpenToken
 | 
					                        OpenToken = mediaSource.OpenToken,
 | 
				
			||||||
 | 
					                        EnableMediaProbe = request.EnableMediaProbe
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    }).ConfigureAwait(false);
 | 
					                    }).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@ using MediaBrowser.Model.Serialization;
 | 
				
			|||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					using MediaBrowser.Controller.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Configuration;
 | 
				
			||||||
using MediaBrowser.Model.IO;
 | 
					using MediaBrowser.Model.IO;
 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					using MediaBrowser.Model.Services;
 | 
				
			||||||
using MediaBrowser.Model.System;
 | 
					using MediaBrowser.Model.System;
 | 
				
			||||||
@ -58,10 +59,8 @@ namespace MediaBrowser.Api.Playback.Progressive
 | 
				
			|||||||
            return ProcessRequest(request, true);
 | 
					            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);
 | 
					            return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@ using MediaBrowser.Model.IO;
 | 
				
			|||||||
using MediaBrowser.Model.Serialization;
 | 
					using MediaBrowser.Model.Serialization;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using MediaBrowser.Controller.Net;
 | 
					using MediaBrowser.Controller.Net;
 | 
				
			||||||
 | 
					using MediaBrowser.Model.Configuration;
 | 
				
			||||||
using MediaBrowser.Model.Services;
 | 
					using MediaBrowser.Model.Services;
 | 
				
			||||||
using MediaBrowser.Model.System;
 | 
					using MediaBrowser.Model.System;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -91,10 +92,8 @@ namespace MediaBrowser.Api.Playback.Progressive
 | 
				
			|||||||
            return ProcessRequest(request, true);
 | 
					            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());
 | 
					            return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultH264Preset());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -80,7 +80,7 @@ namespace MediaBrowser.Api.Playback
 | 
				
			|||||||
                            return 6;
 | 
					                            return 6;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        return 10;
 | 
					                        return 6;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (IsSegmentedLiveStream)
 | 
					                    if (IsSegmentedLiveStream)
 | 
				
			||||||
 | 
				
			|||||||
@ -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")]
 | 
					        [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; }
 | 
					        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; }
 | 
					        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)]
 | 
					        [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)]
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,20 @@ namespace MediaBrowser.Common.Extensions
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class RemoteServiceUnavailableException : Exception
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public RemoteServiceUnavailableException()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public RemoteServiceUnavailableException(string message)
 | 
				
			||||||
 | 
					            : base(message)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public class RateLimitExceededException : Exception
 | 
					    public class RateLimitExceededException : Exception
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
 | 
				
			|||||||
@ -67,7 +67,7 @@ namespace MediaBrowser.Controller.Channels
 | 
				
			|||||||
                Name = id,
 | 
					                Name = id,
 | 
				
			||||||
                Id = id,
 | 
					                Id = id,
 | 
				
			||||||
                ReadAtNativeFramerate = ReadAtNativeFramerate,
 | 
					                ReadAtNativeFramerate = ReadAtNativeFramerate,
 | 
				
			||||||
                SupportsDirectStream = false,
 | 
					                SupportsDirectStream = Protocol == MediaProtocol.Http && !string.IsNullOrWhiteSpace(Container) && !string.Equals(Container, "hls", StringComparison.OrdinalIgnoreCase),
 | 
				
			||||||
                SupportsDirectPlay = SupportsDirectPlay,
 | 
					                SupportsDirectPlay = SupportsDirectPlay,
 | 
				
			||||||
                IsRemote = true
 | 
					                IsRemote = true
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
				
			|||||||
@ -12,18 +12,11 @@ namespace MediaBrowser.Controller.Dlna
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Renews the event subscription.
 | 
					        /// Renews the event subscription.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="subscriptionId">The subscription identifier.</param>
 | 
					        EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string requestedTimeoutString);
 | 
				
			||||||
        /// <param name="timeoutSeconds">The timeout seconds.</param>
 | 
					 | 
				
			||||||
        /// <returns>EventSubscriptionResponse.</returns>
 | 
					 | 
				
			||||||
        EventSubscriptionResponse RenewEventSubscription(string subscriptionId, int? timeoutSeconds);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Creates the event subscription.
 | 
					        /// Creates the event subscription.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="notificationType">Type of the notification.</param>
 | 
					        EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl);
 | 
				
			||||||
        /// <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);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -57,6 +57,22 @@ namespace MediaBrowser.Controller.Entities
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            get
 | 
					            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;
 | 
					                return true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -20,10 +20,7 @@ namespace MediaBrowser.Controller.Library
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Opens the media source.
 | 
					        /// Opens the media source.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <param name="openToken">The open token.</param>
 | 
					        Task<Tuple<MediaSourceInfo,IDirectStreamProvider>> OpenMediaSource(string openToken, bool allowLiveStreamProbe, CancellationToken cancellationToken);
 | 
				
			||||||
        /// <param name="cancellationToken">The cancellation token.</param>
 | 
					 | 
				
			||||||
        /// <returns>Task<MediaSourceInfo>.</returns>
 | 
					 | 
				
			||||||
        Task<Tuple<MediaSourceInfo,IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Closes the media source.
 | 
					        /// Closes the media source.
 | 
				
			||||||
 | 
				
			|||||||
@ -120,10 +120,14 @@ namespace MediaBrowser.Controller.Providers
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                file = _fileSystem.GetFileInfo(path);
 | 
					                file = _fileSystem.GetFileInfo(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (file != null)
 | 
					                if (file != null && file.Exists)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _fileCache.TryAdd(path, file);
 | 
					                    _fileCache.TryAdd(path, file);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return null;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return file;
 | 
					            return file;
 | 
				
			||||||
 | 
				
			|||||||
@ -203,6 +203,11 @@ namespace MediaBrowser.Controller.Session
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public void StartAutomaticProgress(ITimerFactory timerFactory, PlaybackProgressInfo progressInfo)
 | 
					        public void StartAutomaticProgress(ITimerFactory timerFactory, PlaybackProgressInfo progressInfo)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            if (_disposed)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            lock (_progressLock)
 | 
					            lock (_progressLock)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                _lastProgressInfo = progressInfo;
 | 
					                _lastProgressInfo = progressInfo;
 | 
				
			||||||
@ -223,6 +228,11 @@ namespace MediaBrowser.Controller.Session
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private async void OnProgressTimerCallback(object state)
 | 
					        private async void OnProgressTimerCallback(object state)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            if (_disposed)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var progressInfo = _lastProgressInfo;
 | 
					            var progressInfo = _lastProgressInfo;
 | 
				
			||||||
            if (progressInfo == null)
 | 
					            if (progressInfo == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -274,8 +284,12 @@ namespace MediaBrowser.Controller.Session
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private bool _disposed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void Dispose()
 | 
					        public void Dispose()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            _disposed = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            StopAutomaticProgress();
 | 
					            StopAutomaticProgress();
 | 
				
			||||||
            _sessionManager = null;
 | 
					            _sessionManager = null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -90,6 +90,8 @@ namespace MediaBrowser.Controller.Sync
 | 
				
			|||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        IEnumerable<SyncTarget> GetSyncTargets(string userId);
 | 
					        IEnumerable<SyncTarget> GetSyncTargets(string userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        IEnumerable<SyncTarget> GetSyncTargets(string userId, bool? supportsRemoteSync);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Supportses the synchronize.
 | 
					        /// Supportses the synchronize.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,8 @@ namespace MediaBrowser.Controller.Sync
 | 
				
			|||||||
        /// <value>The name.</value>
 | 
					        /// <value>The name.</value>
 | 
				
			||||||
        string Name { get; }
 | 
					        string Name { get; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool SupportsRemoteSync { get; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the synchronize targets.
 | 
					        /// Gets the synchronize targets.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
 | 
				
			|||||||
@ -733,9 +733,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
 | 
					            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);
 | 
					            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;
 | 
					            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) :
 | 
					            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);
 | 
					                string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
using System.Collections.Generic;
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
using System.Xml.Serialization;
 | 
					using System.Xml.Serialization;
 | 
				
			||||||
using MediaBrowser.Model.Dlna;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MediaBrowser.Model.Dlna
 | 
					namespace MediaBrowser.Model.Dlna
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -28,6 +29,19 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
            return list;
 | 
					            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()
 | 
					        public List<string> GetAudioCodecs()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            List<string> list = new List<string>();
 | 
					            List<string> list = new List<string>();
 | 
				
			||||||
 | 
				
			|||||||
@ -491,14 +491,9 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
            var videoSupported = false;
 | 
					            var videoSupported = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            foreach (var profile in directPlayProfiles)
 | 
					            foreach (var profile in directPlayProfiles)
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (profile.Container.Length > 0)
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // Check container type
 | 
					                // Check container type
 | 
				
			||||||
                    string mediaContainer = item.Container ?? string.Empty;
 | 
					                if (profile.SupportsContainer(item.Container))
 | 
				
			||||||
                    foreach (string i in profile.GetContainers())
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        if (StringHelper.EqualsIgnoreCase(i, mediaContainer))
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    containerSupported = true;
 | 
					                    containerSupported = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -539,8 +534,6 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!containerSupported)
 | 
					            if (!containerSupported)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -635,8 +628,10 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
            MediaStream videoStream = item.VideoStream;
 | 
					            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
 | 
					            // 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));
 | 
					            var directPlayEligibilityResult = 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 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}",
 | 
					            _logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
 | 
				
			||||||
                options.Profile.Name ?? "Unknown Profile",
 | 
					                options.Profile.Name ?? "Unknown Profile",
 | 
				
			||||||
@ -669,6 +664,16 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
                transcodeReasons.AddRange(directPlayInfo.Item2);
 | 
					                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
 | 
					            // Can't direct play, find the transcoding profile
 | 
				
			||||||
            TranscodingProfile transcodingProfile = null;
 | 
					            TranscodingProfile transcodingProfile = null;
 | 
				
			||||||
            foreach (TranscodingProfile i in options.Profile.TranscodingProfiles)
 | 
					            foreach (TranscodingProfile i in options.Profile.TranscodingProfiles)
 | 
				
			||||||
@ -1136,7 +1141,7 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
                mediaSource.Path ?? "Unknown path");
 | 
					                mediaSource.Path ?? "Unknown path");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private bool IsEligibleForDirectPlay(MediaSourceInfo item,
 | 
					        private Tuple<bool, TranscodeReason?> IsEligibleForDirectPlay(MediaSourceInfo item,
 | 
				
			||||||
            long? maxBitrate,
 | 
					            long? maxBitrate,
 | 
				
			||||||
            MediaStream subtitleStream,
 | 
					            MediaStream subtitleStream,
 | 
				
			||||||
            VideoOptions options,
 | 
					            VideoOptions options,
 | 
				
			||||||
@ -1149,11 +1154,18 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
                if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
 | 
					                if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _logger.Info("Not eligible for {0} due to unsupported subtitles", playMethod);
 | 
					                    _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)
 | 
					        public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, string transcodingSubProtocol, string transcodingContainer)
 | 
				
			||||||
@ -1518,25 +1530,12 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
 | 
					        private bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (profile.Container.Length > 0)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // Check container type
 | 
					            // Check container type
 | 
				
			||||||
                string mediaContainer = item.Container ?? string.Empty;
 | 
					            if (!profile.SupportsContainer(item.Container))
 | 
				
			||||||
                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
 | 
					            // Check audio codec
 | 
				
			||||||
            List<string> audioCodecs = profile.GetAudioCodecs();
 | 
					            List<string> audioCodecs = profile.GetAudioCodecs();
 | 
				
			||||||
@ -1554,25 +1553,12 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream)
 | 
					        private bool IsVideoDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream)
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (profile.Container.Length > 0)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // Check container type
 | 
					            // Check container type
 | 
				
			||||||
                string mediaContainer = item.Container ?? string.Empty;
 | 
					            if (!profile.SupportsContainer(item.Container))
 | 
				
			||||||
                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
 | 
					            // Check video codec
 | 
				
			||||||
            List<string> videoCodecs = profile.GetVideoCodecs();
 | 
					            List<string> videoCodecs = profile.GetVideoCodecs();
 | 
				
			||||||
 | 
				
			|||||||
@ -17,13 +17,13 @@ namespace MediaBrowser.Model.MediaInfo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public bool EnableDirectPlay { get; set; }
 | 
					        public bool EnableDirectPlay { get; set; }
 | 
				
			||||||
        public bool EnableDirectStream { get; set; }
 | 
					        public bool EnableDirectStream { get; set; }
 | 
				
			||||||
        public bool ForceDirectPlayRemoteMediaSource { get; set; }
 | 
					        public bool EnableMediaProbe { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public LiveStreamRequest()
 | 
					        public LiveStreamRequest()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            ForceDirectPlayRemoteMediaSource = true;
 | 
					 | 
				
			||||||
            EnableDirectPlay = true;
 | 
					            EnableDirectPlay = true;
 | 
				
			||||||
            EnableDirectStream = true;
 | 
					            EnableDirectStream = true;
 | 
				
			||||||
 | 
					            EnableMediaProbe = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public LiveStreamRequest(AudioOptions options)
 | 
					        public LiveStreamRequest(AudioOptions options)
 | 
				
			||||||
 | 
				
			|||||||
@ -27,19 +27,19 @@ namespace MediaBrowser.Model.MediaInfo
 | 
				
			|||||||
        public bool EnableDirectPlay { get; set; }
 | 
					        public bool EnableDirectPlay { get; set; }
 | 
				
			||||||
        public bool EnableDirectStream { get; set; }
 | 
					        public bool EnableDirectStream { get; set; }
 | 
				
			||||||
        public bool EnableTranscoding { get; set; }
 | 
					        public bool EnableTranscoding { get; set; }
 | 
				
			||||||
        public bool ForceDirectPlayRemoteMediaSource { get; set; }
 | 
					 | 
				
			||||||
        public bool AllowVideoStreamCopy { get; set; }
 | 
					        public bool AllowVideoStreamCopy { get; set; }
 | 
				
			||||||
        public bool AllowAudioStreamCopy { get; set; }
 | 
					        public bool AllowAudioStreamCopy { get; set; }
 | 
				
			||||||
        public bool AutoOpenLiveStream { get; set; }
 | 
					        public bool AutoOpenLiveStream { get; set; }
 | 
				
			||||||
 | 
					        public bool EnableMediaProbe { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public PlaybackInfoRequest()
 | 
					        public PlaybackInfoRequest()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            ForceDirectPlayRemoteMediaSource = true;
 | 
					 | 
				
			||||||
            EnableDirectPlay = true;
 | 
					            EnableDirectPlay = true;
 | 
				
			||||||
            EnableDirectStream = true;
 | 
					            EnableDirectStream = true;
 | 
				
			||||||
            EnableTranscoding = true;
 | 
					            EnableTranscoding = true;
 | 
				
			||||||
            AllowVideoStreamCopy = true;
 | 
					            AllowVideoStreamCopy = true;
 | 
				
			||||||
            AllowAudioStreamCopy = true;
 | 
					            AllowAudioStreamCopy = true;
 | 
				
			||||||
 | 
					            EnableMediaProbe = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -48,6 +48,7 @@ namespace MediaBrowser.Model.Session
 | 
				
			|||||||
        VideoFramerateNotSupported = 17,
 | 
					        VideoFramerateNotSupported = 17,
 | 
				
			||||||
        VideoLevelNotSupported = 18,
 | 
					        VideoLevelNotSupported = 18,
 | 
				
			||||||
        VideoProfileNotSupported = 19,
 | 
					        VideoProfileNotSupported = 19,
 | 
				
			||||||
        AudioBitDepthNotSupported = 20
 | 
					        AudioBitDepthNotSupported = 20,
 | 
				
			||||||
 | 
					        SubtitleCodecNotSupported = 21
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -105,9 +105,12 @@ namespace MediaBrowser.Model.Sync
 | 
				
			|||||||
        public string PrimaryImageItemId { get; set; }
 | 
					        public string PrimaryImageItemId { get; set; }
 | 
				
			||||||
        public string PrimaryImageTag { get; set; }
 | 
					        public string PrimaryImageTag { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public bool EnableAutomaticResync { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public SyncJob()
 | 
					        public SyncJob()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            RequestedItemIds = new List<string>();
 | 
					            RequestedItemIds = new List<string>();
 | 
				
			||||||
 | 
					            EnableAutomaticResync = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Music
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
 | 
					            if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (!item.IsLocked)
 | 
					                if (!item.IsLocked && !item.LockedFields.Contains(MetadataFields.Genres))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    var taggedItems = item.IsAccessedByName ?
 | 
					                    var taggedItems = item.IsAccessedByName ?
 | 
				
			||||||
                        item.GetTaggedItems(new Controller.Entities.InternalItemsQuery()
 | 
					                        item.GetTaggedItems(new Controller.Entities.InternalItemsQuery()
 | 
				
			||||||
@ -31,8 +31,6 @@ namespace MediaBrowser.Providers.Music
 | 
				
			|||||||
                        }) :
 | 
					                        }) :
 | 
				
			||||||
                        item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder).ToList();
 | 
					                        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)
 | 
					                    item.Genres = taggedItems.SelectMany(i => i.Genres)
 | 
				
			||||||
@ -45,7 +43,6 @@ namespace MediaBrowser.Providers.Music
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return updateType;
 | 
					            return updateType;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
using MediaBrowser.Controller.Configuration;
 | 
					using System;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Configuration;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
using MediaBrowser.Controller.Playlists;
 | 
					using MediaBrowser.Controller.Playlists;
 | 
				
			||||||
using MediaBrowser.Controller.Providers;
 | 
					using MediaBrowser.Controller.Providers;
 | 
				
			||||||
@ -6,7 +7,7 @@ using MediaBrowser.Model.Entities;
 | 
				
			|||||||
using MediaBrowser.Model.Logging;
 | 
					using MediaBrowser.Model.Logging;
 | 
				
			||||||
using MediaBrowser.Providers.Manager;
 | 
					using MediaBrowser.Providers.Manager;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Linq;
 | 
				
			||||||
using MediaBrowser.Controller.IO;
 | 
					using MediaBrowser.Controller.IO;
 | 
				
			||||||
using MediaBrowser.Model.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)
 | 
					        public PlaylistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -196,8 +196,6 @@ namespace MediaBrowser.Providers.TV
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (HttpException ex)
 | 
					            catch (HttpException ex)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Logger.Error("No metadata found for {0}", seasonNumber.Value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
 | 
					                if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return result;
 | 
					                    return result;
 | 
				
			||||||
 | 
				
			|||||||
@ -116,10 +116,10 @@
 | 
				
			|||||||
      <HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath>
 | 
					      <HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath>
 | 
				
			||||||
    </Reference>
 | 
					    </Reference>
 | 
				
			||||||
    <Reference Include="SQLitePCLRaw.core">
 | 
					    <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>
 | 
				
			||||||
    <Reference Include="SQLitePCLRaw.provider.sqlite3">
 | 
					    <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>
 | 
				
			||||||
    <Reference Include="Emby.Server.Connect">
 | 
					    <Reference Include="Emby.Server.Connect">
 | 
				
			||||||
      <HintPath>..\ThirdParty\emby\Emby.Server.Connect.dll</HintPath>
 | 
					      <HintPath>..\ThirdParty\emby\Emby.Server.Connect.dll</HintPath>
 | 
				
			||||||
@ -411,6 +411,9 @@
 | 
				
			|||||||
    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\mypreferencesmenu.html">
 | 
					    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\mypreferencesmenu.html">
 | 
				
			||||||
      <Link>Resources\dashboard-ui\mypreferencesmenu.html</Link>
 | 
					      <Link>Resources\dashboard-ui\mypreferencesmenu.html</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </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">
 | 
					    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\myprofile.html">
 | 
				
			||||||
      <Link>Resources\dashboard-ui\myprofile.html</Link>
 | 
					      <Link>Resources\dashboard-ui\myprofile.html</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </BundleResource>
 | 
				
			||||||
@ -918,6 +921,12 @@
 | 
				
			|||||||
    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-radio\emby-radio.js">
 | 
					    <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>
 | 
					      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-radio\emby-radio.js</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </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">
 | 
					    <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>
 | 
					      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-scroller\emby-scroller.js</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </BundleResource>
 | 
				
			||||||
@ -1476,6 +1485,15 @@
 | 
				
			|||||||
    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.template.html">
 | 
					    <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>
 | 
					      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.template.html</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </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">
 | 
					    <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>
 | 
					      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sync\emby-downloadbutton.js</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </BundleResource>
 | 
				
			||||||
@ -1488,15 +1506,18 @@
 | 
				
			|||||||
    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sync\syncjoblist.js">
 | 
					    <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>
 | 
					      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sync\syncjoblist.js</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </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">
 | 
					    <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>
 | 
					      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\toast\toast.css</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </BundleResource>
 | 
				
			||||||
    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\toast\toast.js">
 | 
					    <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>
 | 
					      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\toast\toast.js</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </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">
 | 
					    <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>
 | 
					      <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\userdatabuttons\emby-playstatebutton.js</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </BundleResource>
 | 
				
			||||||
@ -1788,9 +1809,6 @@
 | 
				
			|||||||
    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\librarybrowser.css">
 | 
					    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\librarybrowser.css">
 | 
				
			||||||
      <Link>Resources\dashboard-ui\css\librarybrowser.css</Link>
 | 
					      <Link>Resources\dashboard-ui\css\librarybrowser.css</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </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">
 | 
					    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\livetv.css">
 | 
				
			||||||
      <Link>Resources\dashboard-ui\css\livetv.css</Link>
 | 
					      <Link>Resources\dashboard-ui\css\livetv.css</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </BundleResource>
 | 
				
			||||||
@ -2082,84 +2100,6 @@
 | 
				
			|||||||
    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\ios\ios.css">
 | 
					    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\ios\ios.css">
 | 
				
			||||||
      <Link>Resources\dashboard-ui\devices\ios\ios.css</Link>
 | 
					      <Link>Resources\dashboard-ui\devices\ios\ios.css</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </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">
 | 
					    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\buttonenabled.js">
 | 
				
			||||||
      <Link>Resources\dashboard-ui\legacy\buttonenabled.js</Link>
 | 
					      <Link>Resources\dashboard-ui\legacy\buttonenabled.js</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </BundleResource>
 | 
				
			||||||
@ -2358,6 +2298,9 @@
 | 
				
			|||||||
    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\mypreferenceslanguages.js">
 | 
					    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\mypreferenceslanguages.js">
 | 
				
			||||||
      <Link>Resources\dashboard-ui\scripts\mypreferenceslanguages.js</Link>
 | 
					      <Link>Resources\dashboard-ui\scripts\mypreferenceslanguages.js</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </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">
 | 
					    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\myprofile.js">
 | 
				
			||||||
      <Link>Resources\dashboard-ui\scripts\myprofile.js</Link>
 | 
					      <Link>Resources\dashboard-ui\scripts\myprofile.js</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </BundleResource>
 | 
				
			||||||
@ -2658,9 +2601,6 @@
 | 
				
			|||||||
    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\themes\holiday\theme.js">
 | 
					    <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\themes\holiday\theme.js">
 | 
				
			||||||
      <Link>Resources\dashboard-ui\themes\holiday\theme.js</Link>
 | 
					      <Link>Resources\dashboard-ui\themes\holiday\theme.js</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </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">
 | 
					    <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>
 | 
					      <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.listview.css</Link>
 | 
				
			||||||
    </BundleResource>
 | 
					    </BundleResource>
 | 
				
			||||||
 | 
				
			|||||||
@ -71,11 +71,11 @@
 | 
				
			|||||||
      <Private>True</Private>
 | 
					      <Private>True</Private>
 | 
				
			||||||
    </Reference>
 | 
					    </Reference>
 | 
				
			||||||
    <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
 | 
					    <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>
 | 
					      <Private>True</Private>
 | 
				
			||||||
    </Reference>
 | 
					    </Reference>
 | 
				
			||||||
    <Reference Include="SQLitePCLRaw.provider.sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=62684c7b4f184e3f, processorArchitecture=MSIL">
 | 
					    <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>
 | 
					      <Private>True</Private>
 | 
				
			||||||
    </Reference>
 | 
					    </Reference>
 | 
				
			||||||
    <Reference Include="System" />
 | 
					    <Reference Include="System" />
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,6 @@
 | 
				
			|||||||
  <package id="SharpCompress" version="0.14.0" targetFramework="net46" />
 | 
					  <package id="SharpCompress" version="0.14.0" targetFramework="net46" />
 | 
				
			||||||
  <package id="SimpleInjector" version="4.0.8" targetFramework="net46" />
 | 
					  <package id="SimpleInjector" version="4.0.8" targetFramework="net46" />
 | 
				
			||||||
  <package id="SkiaSharp" version="1.58.0" targetFramework="net46" />
 | 
					  <package id="SkiaSharp" version="1.58.0" targetFramework="net46" />
 | 
				
			||||||
  <package id="SQLitePCLRaw.core" 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.6" targetFramework="net46" />
 | 
					  <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.7" targetFramework="net46" />
 | 
				
			||||||
</packages>
 | 
					</packages>
 | 
				
			||||||
@ -770,16 +770,39 @@ namespace MediaBrowser.ServerApplication
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var subkey = Environment.Is64BitProcess
 | 
					                RegistryKey key;
 | 
				
			||||||
                    ? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64"
 | 
					 | 
				
			||||||
                    : "SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x86";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
 | 
					                if (Environment.Is64BitProcess)
 | 
				
			||||||
                    .OpenSubKey(subkey))
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    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');
 | 
					                        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)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            var installedVersion = ((string)version).TrimStart('v');
 | 
				
			||||||
                            if (installedVersion.StartsWith("14", StringComparison.OrdinalIgnoreCase))
 | 
					                            if (installedVersion.StartsWith("14", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                            {
 | 
					                            {
 | 
				
			||||||
                                return;
 | 
					                                return;
 | 
				
			||||||
@ -787,6 +810,7 @@ namespace MediaBrowser.ServerApplication
 | 
				
			|||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            catch (Exception ex)
 | 
					            catch (Exception ex)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                logger.ErrorException("Error getting .NET Framework version", ex);
 | 
					                logger.ErrorException("Error getting .NET Framework version", ex);
 | 
				
			||||||
 | 
				
			|||||||
@ -73,10 +73,6 @@
 | 
				
			|||||||
    <Reference Include="Emby.Server.Sync">
 | 
					    <Reference Include="Emby.Server.Sync">
 | 
				
			||||||
      <HintPath>..\ThirdParty\emby\Emby.Server.Sync.dll</HintPath>
 | 
					      <HintPath>..\ThirdParty\emby\Emby.Server.Sync.dll</HintPath>
 | 
				
			||||||
    </Reference>
 | 
					    </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">
 | 
					    <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>
 | 
					      <HintPath>..\packages\NLog.4.4.11\lib\net45\NLog.dll</HintPath>
 | 
				
			||||||
      <Private>True</Private>
 | 
					      <Private>True</Private>
 | 
				
			||||||
@ -96,11 +92,11 @@
 | 
				
			|||||||
      <HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath>
 | 
					      <HintPath>..\packages\SkiaSharp.1.58.0\lib\net45\SkiaSharp.dll</HintPath>
 | 
				
			||||||
    </Reference>
 | 
					    </Reference>
 | 
				
			||||||
    <Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
 | 
					    <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>
 | 
					      <Private>True</Private>
 | 
				
			||||||
    </Reference>
 | 
					    </Reference>
 | 
				
			||||||
    <Reference Include="SQLitePCLRaw.provider.sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=62684c7b4f184e3f, processorArchitecture=MSIL">
 | 
					    <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>
 | 
					      <Private>True</Private>
 | 
				
			||||||
    </Reference>
 | 
					    </Reference>
 | 
				
			||||||
    <Reference Include="System" />
 | 
					    <Reference Include="System" />
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,10 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="utf-8"?>
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
<packages>
 | 
					<packages>
 | 
				
			||||||
  <package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" />
 | 
					 | 
				
			||||||
  <package id="NLog" version="4.4.11" targetFramework="net462" />
 | 
					  <package id="NLog" version="4.4.11" targetFramework="net462" />
 | 
				
			||||||
  <package id="ServiceStack.Text" version="4.5.8" targetFramework="net462" />
 | 
					  <package id="ServiceStack.Text" version="4.5.8" targetFramework="net462" />
 | 
				
			||||||
  <package id="SharpCompress" version="0.14.0" targetFramework="net462" />
 | 
					  <package id="SharpCompress" version="0.14.0" targetFramework="net462" />
 | 
				
			||||||
  <package id="SimpleInjector" version="4.0.8" targetFramework="net462" />
 | 
					  <package id="SimpleInjector" version="4.0.8" targetFramework="net462" />
 | 
				
			||||||
  <package id="SkiaSharp" version="1.58.0" targetFramework="net462" />
 | 
					  <package id="SkiaSharp" version="1.58.0" targetFramework="net462" />
 | 
				
			||||||
  <package id="SQLitePCLRaw.core" 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.6" targetFramework="net462" />
 | 
					  <package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.7" targetFramework="net462" />
 | 
				
			||||||
</packages>
 | 
					</packages>
 | 
				
			||||||
@ -242,10 +242,7 @@ namespace MediaBrowser.WebDashboard.Api
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var files = new[]
 | 
					            var files = new[]
 | 
				
			||||||
                            {
 | 
					                            {
 | 
				
			||||||
                                      "css/site.css" + versionString,
 | 
					                                      "css/site.css" + versionString
 | 
				
			||||||
                                      "css/librarymenu.css" + versionString,
 | 
					 | 
				
			||||||
                                      "css/librarybrowser.css" + versionString,
 | 
					 | 
				
			||||||
                                      "thirdparty/paper-button-style.css" + versionString
 | 
					 | 
				
			||||||
                            };
 | 
					                            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var tags = files.Select(s => string.Format("<link rel=\"stylesheet\" href=\"{0}\" async />", s)).ToArray();
 | 
					            var tags = files.Select(s => string.Format("<link rel=\"stylesheet\" href=\"{0}\" async />", s)).ToArray();
 | 
				
			||||||
 | 
				
			|||||||
@ -16,9 +16,10 @@ using System.Globalization;
 | 
				
			|||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Text;
 | 
					using System.Text;
 | 
				
			||||||
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Xml;
 | 
					using System.Xml;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Extensions;
 | 
				
			||||||
using MediaBrowser.Model.Extensions;
 | 
					using MediaBrowser.Model.Extensions;
 | 
				
			||||||
using MediaBrowser.Model.IO;
 | 
					using MediaBrowser.Model.IO;
 | 
				
			||||||
using MediaBrowser.Model.Xml;
 | 
					using MediaBrowser.Model.Xml;
 | 
				
			||||||
@ -353,7 +354,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                if (!string.IsNullOrEmpty(stream.Language))
 | 
					                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";
 | 
					                var scanType = stream.IsInterlaced ? "interlaced" : "progressive";
 | 
				
			||||||
@ -422,6 +424,19 @@ namespace MediaBrowser.XbmcMetadata.Savers
 | 
				
			|||||||
            writer.WriteEndElement();
 | 
					            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";
 | 
					        public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
 | 
				
			|||||||
@ -51,6 +51,11 @@ namespace MediaBrowser.XbmcMetadata.Savers
 | 
				
			|||||||
                //}
 | 
					                //}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                list.Add(Path.ChangeExtension(item.Path, ".nfo"));
 | 
					                list.Add(Path.ChangeExtension(item.Path, ".nfo"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!item.IsInMixedFolder)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    list.Add(Path.Combine(item.ContainingFolderPath, "movie.nfo"));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return list;
 | 
					            return list;
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 | 
					<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 | 
				
			||||||
    <metadata>
 | 
					    <metadata>
 | 
				
			||||||
        <id>MediaBrowser.Common</id>
 | 
					        <id>MediaBrowser.Common</id>
 | 
				
			||||||
        <version>3.0.704</version>
 | 
					        <version>3.0.708</version>
 | 
				
			||||||
        <title>Emby.Common</title>
 | 
					        <title>Emby.Common</title>
 | 
				
			||||||
        <authors>Emby Team</authors>
 | 
					        <authors>Emby Team</authors>
 | 
				
			||||||
        <owners>ebr,Luke,scottisafool</owners>
 | 
					        <owners>ebr,Luke,scottisafool</owners>
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
 | 
					<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
 | 
				
			||||||
    <metadata>
 | 
					    <metadata>
 | 
				
			||||||
        <id>MediaBrowser.Server.Core</id>
 | 
					        <id>MediaBrowser.Server.Core</id>
 | 
				
			||||||
        <version>3.0.704</version>
 | 
					        <version>3.0.708</version>
 | 
				
			||||||
        <title>Emby.Server.Core</title>
 | 
					        <title>Emby.Server.Core</title>
 | 
				
			||||||
        <authors>Emby Team</authors>
 | 
					        <authors>Emby Team</authors>
 | 
				
			||||||
        <owners>ebr,Luke,scottisafool</owners>
 | 
					        <owners>ebr,Luke,scottisafool</owners>
 | 
				
			||||||
@ -12,7 +12,7 @@
 | 
				
			|||||||
        <description>Contains core components required to build plugins for Emby Server.</description>
 | 
					        <description>Contains core components required to build plugins for Emby Server.</description>
 | 
				
			||||||
        <copyright>Copyright © Emby 2013</copyright>
 | 
					        <copyright>Copyright © Emby 2013</copyright>
 | 
				
			||||||
        <dependencies>
 | 
					        <dependencies>
 | 
				
			||||||
            <dependency id="MediaBrowser.Common" version="3.0.704" />
 | 
					            <dependency id="MediaBrowser.Common" version="3.0.708" />
 | 
				
			||||||
        </dependencies>
 | 
					        </dependencies>
 | 
				
			||||||
    </metadata>
 | 
					    </metadata>
 | 
				
			||||||
    <files>
 | 
					    <files>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user