diff --git a/MediaBrowser.Api/AppThemeService.cs b/MediaBrowser.Api/AppThemeService.cs index 54141b3e22..4d8eed7ddb 100644 --- a/MediaBrowser.Api/AppThemeService.cs +++ b/MediaBrowser.Api/AppThemeService.cs @@ -9,16 +9,14 @@ using System.Linq; namespace MediaBrowser.Api { - [Route("/Themes", "GET")] - [Api(Description = "Gets a list of available themes for an app")] + [Route("/Themes", "GET", Summary = "Gets a list of available themes for an app")] public class GetAppThemes : IReturn> { [ApiMember(Name = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] public string App { get; set; } } - [Route("/Themes/Info", "GET")] - [Api(Description = "Gets an app theme")] + [Route("/Themes/Info", "GET", Summary = "Gets an app theme")] public class GetAppTheme : IReturn { [ApiMember(Name = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -28,8 +26,7 @@ namespace MediaBrowser.Api public string Name { get; set; } } - [Route("/Themes/Images", "GET")] - [Api(Description = "Gets an app theme")] + [Route("/Themes/Images", "GET", Summary = "Gets an app theme")] public class GetAppThemeImage { [ApiMember(Name = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -45,12 +42,11 @@ namespace MediaBrowser.Api public string CacheTag { get; set; } } - [Route("/Themes", "POST")] - [Api(Description = "Saves a theme")] + [Route("/Themes", "POST", Summary = "Saves a theme")] public class SaveTheme : AppTheme, IReturnVoid { } - + public class AppThemeService : BaseApiService { private readonly IAppThemeManager _themeManager; diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 08686b43a8..707cfb4572 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; using ServiceStack.Web; using System; @@ -78,6 +79,20 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } + /// + /// Gets the session. + /// + /// The session manager. + /// SessionInfo. + protected SessionInfo GetSession(ISessionManager sessionManager) + { + var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); + + return sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) && + string.Equals(i.Client, auth.Client) && + string.Equals(i.ApplicationVersion, auth.Version)); + } + /// /// To the cached result. /// diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index c1724571ae..ed203a0f45 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -7,8 +7,7 @@ using System.Threading; namespace MediaBrowser.Api { - [Route("/Channels", "GET")] - [Api(("Gets available channels"))] + [Route("/Channels", "GET", Summary = "Gets available channels")] public class GetChannels : IReturn> { public string UserId { get; set; } @@ -18,8 +17,7 @@ namespace MediaBrowser.Api public int? Limit { get; set; } } - [Route("/Channels/{Id}/Items", "GET")] - [Api(("Gets channel items"))] + [Route("/Channels/{Id}/Items", "GET", Summary = "Gets channel items")] public class GetChannelItems : IReturn> { public string Id { get; set; } diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 6007043503..b3191cd4b9 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -17,8 +17,7 @@ namespace MediaBrowser.Api /// /// Class GetConfiguration /// - [Route("/System/Configuration", "GET")] - [Api(("Gets application configuration"))] + [Route("/System/Configuration", "GET", Summary = "Gets application configuration")] public class GetConfiguration : IReturn { @@ -27,28 +26,24 @@ namespace MediaBrowser.Api /// /// Class UpdateConfiguration /// - [Route("/System/Configuration", "POST")] - [Api(("Updates application configuration"))] + [Route("/System/Configuration", "POST", Summary = "Updates application configuration")] public class UpdateConfiguration : ServerConfiguration, IReturnVoid { } - [Route("/System/Configuration/MetadataOptions/Default", "GET")] - [Api(("Gets a default MetadataOptions object"))] + [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")] public class GetDefaultMetadataOptions : IReturn { } - [Route("/System/Configuration/MetadataPlugins", "GET")] - [Api(("Gets all available metadata plugins"))] + [Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")] public class GetMetadataPlugins : IReturn> { } - [Route("/System/Configuration/VideoImageExtraction", "POST")] - [Api(("Updates image extraction for all types"))] + [Route("/System/Configuration/VideoImageExtraction", "POST", Summary = "Updates image extraction for all types")] public class UpdateVideoImageExtraction : IReturnVoid { public bool Enabled { get; set; } diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index f22dc9e39e..4060b42b49 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -12,8 +12,7 @@ namespace MediaBrowser.Api /// /// Class UpdateDisplayPreferences /// - [Route("/DisplayPreferences/{DisplayPreferencesId}", "POST")] - [Api(("Updates a user's display preferences for an item"))] + [Route("/DisplayPreferences/{DisplayPreferencesId}", "POST", Summary = "Updates a user's display preferences for an item")] public class UpdateDisplayPreferences : DisplayPreferences, IReturnVoid { /// @@ -30,8 +29,7 @@ namespace MediaBrowser.Api public string Client { get; set; } } - [Route("/DisplayPreferences/{Id}", "GET")] - [Api(("Gets a user's display preferences for an item"))] + [Route("/DisplayPreferences/{Id}", "GET", Summary = "Gets a user's display preferences for an item")] public class GetDisplayPreferences : IReturn { /// diff --git a/MediaBrowser.Api/DlnaService.cs b/MediaBrowser.Api/DlnaService.cs new file mode 100644 index 0000000000..792a7ff43f --- /dev/null +++ b/MediaBrowser.Api/DlnaService.cs @@ -0,0 +1,90 @@ +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Model.Dlna; +using ServiceStack; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Api +{ + [Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")] + public class GetProfileInfos : IReturn> + { + } + + [Route("/Dlna/Profiles/{Id}", "DELETE", Summary = "Deletes a profile")] + public class DeleteProfile : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + } + + [Route("/Dlna/Profiles/Default", "GET", Summary = "Gets the default profile")] + public class GetDefaultProfile : IReturn + { + } + + [Route("/Dlna/Profiles/{Id}", "GET", Summary = "Gets a single profile")] + public class GetProfile : IReturn + { + [ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/Dlna/Profiles/{ProfileId}", "POST", Summary = "Updates a profile")] + public class UpdateProfile : DeviceProfile, IReturnVoid + { + [ApiMember(Name = "ProfileId", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string ProfileId { get; set; } + } + + [Route("/Dlna/Profiles", "POST", Summary = "Creates a profile")] + public class CreateProfile : DeviceProfile, IReturnVoid + { + } + + public class DlnaService : BaseApiService + { + private readonly IDlnaManager _dlnaManager; + + public DlnaService(IDlnaManager dlnaManager) + { + _dlnaManager = dlnaManager; + } + + public object Get(GetProfileInfos request) + { + var result = _dlnaManager.GetProfileInfos().ToList(); + + return ToOptimizedResult(result); + } + + public object Get(GetProfile request) + { + var result = _dlnaManager.GetProfile(request.Id); + + return ToOptimizedResult(result); + } + + public object Get(GetDefaultProfile request) + { + var result = _dlnaManager.GetDefaultProfile(); + + return ToOptimizedResult(result); + } + + public void Delete(DeleteProfile request) + { + _dlnaManager.DeleteProfile(request.Id); + } + + public void Post(UpdateProfile request) + { + _dlnaManager.UpdateProfile(request); + } + + public void Post(CreateProfile request) + { + _dlnaManager.CreateProfile(request); + } + } +} diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index cb104072bd..56f71fc00e 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -13,8 +13,7 @@ namespace MediaBrowser.Api /// /// Class GetDirectoryContents /// - [Route("/Environment/DirectoryContents", "GET")] - [Api(Description = "Gets the contents of a given directory in the file system")] + [Route("/Environment/DirectoryContents", "GET", Summary = "Gets the contents of a given directory in the file system")] public class GetDirectoryContents : IReturn> { /// @@ -46,8 +45,7 @@ namespace MediaBrowser.Api public bool IncludeHidden { get; set; } } - [Route("/Environment/NetworkShares", "GET")] - [Api(Description = "Gets shares from a network device")] + [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")] public class GetNetworkShares : IReturn> { /// @@ -61,8 +59,7 @@ namespace MediaBrowser.Api /// /// Class GetDrives /// - [Route("/Environment/Drives", "GET")] - [Api(Description = "Gets available drives from the server's file system")] + [Route("/Environment/Drives", "GET", Summary = "Gets available drives from the server's file system")] public class GetDrives : IReturn> { } @@ -70,14 +67,12 @@ namespace MediaBrowser.Api /// /// Class GetNetworkComputers /// - [Route("/Environment/NetworkDevices", "GET")] - [Api(Description = "Gets a list of devices on the network")] + [Route("/Environment/NetworkDevices", "GET", Summary = "Gets a list of devices on the network")] public class GetNetworkDevices : IReturn> { } - [Route("/Environment/ParentPath", "GET")] - [Api(Description = "Gets the parent path of a given path")] + [Route("/Environment/ParentPath", "GET", Summary = "Gets the parent path of a given path")] public class GetParentPath : IReturn { /// diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index e371791f81..ff2771ce1b 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; @@ -7,6 +6,7 @@ using MediaBrowser.Model.Dto; using ServiceStack; using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; @@ -15,8 +15,7 @@ namespace MediaBrowser.Api /// /// Class GetSimilarGames /// - [Route("/Games/{Id}/Similar", "GET")] - [Api(Description = "Finds games similar to a given game.")] + [Route("/Games/{Id}/Similar", "GET", Summary = "Finds games similar to a given game.")] public class GetSimilarGames : BaseGetSimilarItemsFromItem { } @@ -24,8 +23,7 @@ namespace MediaBrowser.Api /// /// Class GetGameSystemSummaries /// - [Route("/Games/SystemSummaries", "GET")] - [Api(Description = "Finds games similar to a given game.")] + [Route("/Games/SystemSummaries", "GET", Summary = "Finds games similar to a given game.")] public class GetGameSystemSummaries : IReturn> { /// @@ -39,8 +37,7 @@ namespace MediaBrowser.Api /// /// Class GetGameSystemSummaries /// - [Route("/Games/PlayerIndex", "GET")] - [Api(Description = "Gets an index of players (1-x) and the number of games listed under each")] + [Route("/Games/PlayerIndex", "GET", Summary = "Gets an index of players (1-x) and the number of games listed under each")] public class GetPlayerIndex : IReturn> { /// @@ -117,7 +114,7 @@ namespace MediaBrowser.Api } private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - + public object Get(GetPlayerIndex request) { var games = GetAllLibraryItems(request.UserId, _userManager, _libraryManager) diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 2cdaef1ed8..562da40ee5 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -15,7 +15,6 @@ using ServiceStack.Text.Controller; using ServiceStack.Web; using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; using System.Threading; @@ -776,15 +775,6 @@ namespace MediaBrowser.Api.Images var bytes = Convert.FromBase64String(text); - // Validate first - using (var validationStream = new MemoryStream(bytes)) - { - // This will throw an exception if it's not a valid image - using (Image.FromStream(validationStream)) - { - } - } - var memoryStream = new MemoryStream(bytes) { Position = 0 diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index dba256418e..533a92fba8 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -7,6 +7,7 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Session; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -245,12 +246,13 @@ namespace MediaBrowser.Api.Library private readonly IDtoService _dtoService; private readonly IChannelManager _channelManager; + private readonly ISessionManager _sessionManager; /// /// Initializes a new instance of the class. /// public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, - IDtoService dtoService, IUserDataManager userDataManager, IChannelManager channelManager) + IDtoService dtoService, IUserDataManager userDataManager, IChannelManager channelManager, ISessionManager sessionManager) { _itemRepo = itemRepo; _libraryManager = libraryManager; @@ -258,6 +260,7 @@ namespace MediaBrowser.Api.Library _dtoService = dtoService; _userDataManager = userDataManager; _channelManager = channelManager; + _sessionManager = sessionManager; } public object Get(GetMediaFolders request) @@ -504,6 +507,13 @@ namespace MediaBrowser.Api.Library { var item = _dtoService.GetItemByDtoId(request.Id); + var session = GetSession(_sessionManager); + + if (!session.UserId.HasValue || !_userManager.GetUserById(session.UserId.Value).Configuration.EnableContentDeletion) + { + throw new UnauthorizedAccessException("This operation requires a logged in user with delete access."); + } + return _libraryManager.DeleteItem(item); } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 569e4b52b9..96fe01ab32 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -12,14 +12,12 @@ using System.Threading.Tasks; namespace MediaBrowser.Api.LiveTv { - [Route("/LiveTv/Info", "GET")] - [Api(Description = "Gets available live tv services.")] + [Route("/LiveTv/Info", "GET", Summary = "Gets available live tv services.")] public class GetLiveTvInfo : IReturn { } - [Route("/LiveTv/Channels", "GET")] - [Api(Description = "Gets available live tv channels.")] + [Route("/LiveTv/Channels", "GET", Summary = "Gets available live tv channels.")] public class GetChannels : IReturn> { [ApiMember(Name = "Type", Description = "Optional filter by channel type.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -43,8 +41,7 @@ namespace MediaBrowser.Api.LiveTv public int? Limit { get; set; } } - [Route("/LiveTv/Channels/{Id}", "GET")] - [Api(Description = "Gets a live tv channel")] + [Route("/LiveTv/Channels/{Id}", "GET", Summary = "Gets a live tv channel")] public class GetChannel : IReturn { /// @@ -58,8 +55,7 @@ namespace MediaBrowser.Api.LiveTv public string UserId { get; set; } } - [Route("/LiveTv/Recordings", "GET")] - [Api(Description = "Gets live tv recordings")] + [Route("/LiveTv/Recordings", "GET", Summary = "Gets live tv recordings")] public class GetRecordings : IReturn> { [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -87,16 +83,14 @@ namespace MediaBrowser.Api.LiveTv public string SeriesTimerId { get; set; } } - [Route("/LiveTv/Recordings/Groups", "GET")] - [Api(Description = "Gets live tv recording groups")] + [Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")] public class GetRecordingGroups : IReturn> { [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string UserId { get; set; } } - [Route("/LiveTv/Recordings/{Id}", "GET")] - [Api(Description = "Gets a live tv recording")] + [Route("/LiveTv/Recordings/{Id}", "GET", Summary = "Gets a live tv recording")] public class GetRecording : IReturn { [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] @@ -106,32 +100,28 @@ namespace MediaBrowser.Api.LiveTv public string UserId { get; set; } } - [Route("/LiveTv/Tuners/{Id}/Reset", "POST")] - [Api(Description = "Resets a tv tuner")] + [Route("/LiveTv/Tuners/{Id}/Reset", "POST", Summary = "Resets a tv tuner")] public class ResetTuner : IReturnVoid { [ApiMember(Name = "Id", Description = "Tuner Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } - [Route("/LiveTv/Timers/{Id}", "GET")] - [Api(Description = "Gets a live tv timer")] + [Route("/LiveTv/Timers/{Id}", "GET", Summary = "Gets a live tv timer")] public class GetTimer : IReturn { [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } - [Route("/LiveTv/Timers/Defaults", "GET")] - [Api(Description = "Gets default values for a new timer")] + [Route("/LiveTv/Timers/Defaults", "GET", Summary = "Gets default values for a new timer")] public class GetDefaultTimer : IReturn { [ApiMember(Name = "ProgramId", Description = "Optional, to attach default values based on a program.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string ProgramId { get; set; } } - [Route("/LiveTv/Timers", "GET")] - [Api(Description = "Gets live tv timers")] + [Route("/LiveTv/Timers", "GET", Summary = "Gets live tv timers")] public class GetTimers : IReturn> { [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -141,8 +131,7 @@ namespace MediaBrowser.Api.LiveTv public string SeriesTimerId { get; set; } } - [Route("/LiveTv/Programs", "GET,POST")] - [Api(Description = "Gets available live tv epgs..")] + [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")] public class GetPrograms : IReturn> { [ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] @@ -164,8 +153,7 @@ namespace MediaBrowser.Api.LiveTv public string MaxEndDate { get; set; } } - [Route("/LiveTv/Programs/Recommended", "GET")] - [Api(Description = "Gets available live tv epgs..")] + [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")] public class GetRecommendedPrograms : IReturn> { [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] @@ -181,8 +169,7 @@ namespace MediaBrowser.Api.LiveTv public bool? HasAired { get; set; } } - [Route("/LiveTv/Programs/{Id}", "GET")] - [Api(Description = "Gets a live tv program")] + [Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")] public class GetProgram : IReturn { [ApiMember(Name = "Id", Description = "Program Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] @@ -193,44 +180,38 @@ namespace MediaBrowser.Api.LiveTv } - [Route("/LiveTv/Recordings/{Id}", "DELETE")] - [Api(Description = "Deletes a live tv recording")] + [Route("/LiveTv/Recordings/{Id}", "DELETE", Summary = "Deletes a live tv recording")] public class DeleteRecording : IReturnVoid { [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } - [Route("/LiveTv/Timers/{Id}", "DELETE")] - [Api(Description = "Cancels a live tv timer")] + [Route("/LiveTv/Timers/{Id}", "DELETE", Summary = "Cancels a live tv timer")] public class CancelTimer : IReturnVoid { [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } - [Route("/LiveTv/Timers/{Id}", "POST")] - [Api(Description = "Updates a live tv timer")] + [Route("/LiveTv/Timers/{Id}", "POST", Summary = "Updates a live tv timer")] public class UpdateTimer : TimerInfoDto, IReturnVoid { } - [Route("/LiveTv/Timers", "POST")] - [Api(Description = "Creates a live tv timer")] + [Route("/LiveTv/Timers", "POST", Summary = "Creates a live tv timer")] public class CreateTimer : TimerInfoDto, IReturnVoid { } - [Route("/LiveTv/SeriesTimers/{Id}", "GET")] - [Api(Description = "Gets a live tv series timer")] + [Route("/LiveTv/SeriesTimers/{Id}", "GET", Summary = "Gets a live tv series timer")] public class GetSeriesTimer : IReturn { [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } - [Route("/LiveTv/SeriesTimers", "GET")] - [Api(Description = "Gets live tv series timers")] + [Route("/LiveTv/SeriesTimers", "GET", Summary = "Gets live tv series timers")] public class GetSeriesTimers : IReturn> { [ApiMember(Name = "SortBy", Description = "Optional. Sort by SortName or Priority", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] @@ -240,36 +221,31 @@ namespace MediaBrowser.Api.LiveTv public SortOrder SortOrder { get; set; } } - [Route("/LiveTv/SeriesTimers/{Id}", "DELETE")] - [Api(Description = "Cancels a live tv series timer")] + [Route("/LiveTv/SeriesTimers/{Id}", "DELETE", Summary = "Cancels a live tv series timer")] public class CancelSeriesTimer : IReturnVoid { [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } - [Route("/LiveTv/SeriesTimers/{Id}", "POST")] - [Api(Description = "Updates a live tv series timer")] + [Route("/LiveTv/SeriesTimers/{Id}", "POST", Summary = "Updates a live tv series timer")] public class UpdateSeriesTimer : SeriesTimerInfoDto, IReturnVoid { } - [Route("/LiveTv/SeriesTimers", "POST")] - [Api(Description = "Creates a live tv series timer")] + [Route("/LiveTv/SeriesTimers", "POST", Summary = "Creates a live tv series timer")] public class CreateSeriesTimer : SeriesTimerInfoDto, IReturnVoid { } - [Route("/LiveTv/Recordings/Groups/{Id}", "GET")] - [Api(Description = "Gets a recording group")] + [Route("/LiveTv/Recordings/Groups/{Id}", "GET", Summary = "Gets a recording group")] public class GetRecordingGroup : IReturn { [ApiMember(Name = "Id", Description = "Recording group Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } - [Route("/LiveTv/GuideInfo", "GET")] - [Api(Description = "Gets guide info")] + [Route("/LiveTv/GuideInfo", "GET", Summary = "Gets guide info")] public class GetGuideInfo : IReturn { } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 38cf39b54f..18559a68df 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -53,7 +53,6 @@ - ..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll @@ -67,6 +66,7 @@ Properties\SharedVersion.cs + diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 204a7aab45..228dc378b5 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -17,8 +17,7 @@ namespace MediaBrowser.Api.Movies /// /// Class GetSimilarMovies /// - [Route("/Movies/{Id}/Similar", "GET")] - [Api(Description = "Finds movies and trailers similar to a given movie.")] + [Route("/Movies/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given movie.")] public class GetSimilarMovies : BaseGetSimilarItemsFromItem { [ApiMember(Name = "IncludeTrailers", Description = "Whether or not to include trailers within the results. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] @@ -30,8 +29,7 @@ namespace MediaBrowser.Api.Movies } } - [Route("/Movies/Recommendations", "GET")] - [Api(Description = "Gets movie recommendations")] + [Route("/Movies/Recommendations", "GET", Summary = "Gets movie recommendations")] public class GetMovieRecommendations : IReturn, IHasItemFields { [ApiMember(Name = "CategoryLimit", Description = "The max number of categories to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index 0575066350..05e6a95774 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -10,8 +10,7 @@ namespace MediaBrowser.Api.Movies /// /// Class GetSimilarTrailers /// - [Route("/Trailers/{Id}/Similar", "GET")] - [Api(Description = "Finds movies and trailers similar to a given trailer.")] + [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] public class GetSimilarTrailers : BaseGetSimilarItemsFromItem { } diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index a8446a7ef2..9b9df3a92c 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -9,28 +9,24 @@ using System.Linq; namespace MediaBrowser.Api.Music { - [Route("/Songs/{Id}/InstantMix", "GET")] - [Api(Description = "Creates an instant playlist based on a given song")] + [Route("/Songs/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given song")] public class GetInstantMixFromSong : BaseGetSimilarItemsFromItem { } - [Route("/Albums/{Id}/InstantMix", "GET")] - [Api(Description = "Creates an instant playlist based on a given album")] + [Route("/Albums/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given album")] public class GetInstantMixFromAlbum : BaseGetSimilarItemsFromItem { } - [Route("/Artists/{Name}/InstantMix", "GET")] - [Api(Description = "Creates an instant playlist based on a given artist")] + [Route("/Artists/{Name}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given artist")] public class GetInstantMixFromArtist : BaseGetSimilarItems { [ApiMember(Name = "Name", Description = "The artist name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Name { get; set; } } - [Route("/MusicGenres/{Name}/InstantMix", "GET")] - [Api(Description = "Creates an instant playlist based on a music genre")] + [Route("/MusicGenres/{Name}/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")] public class GetInstantMixFromMusicGenre : BaseGetSimilarItems { [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 7dcb06f7b3..519ff7947a 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -65,6 +66,7 @@ namespace MediaBrowser.Api.Playback protected IItemRepository ItemRepository { get; private set; } protected ILiveTvManager LiveTvManager { get; private set; } + protected IDlnaManager DlnaManager { get; private set; } /// /// Initializes a new instance of the class. @@ -77,8 +79,9 @@ namespace MediaBrowser.Api.Playback /// The dto service. /// The file system. /// The item repository. - protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager) + protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager) { + DlnaManager = dlnaManager; EncodingManager = encodingManager; LiveTvManager = liveTvManager; ItemRepository = itemRepository; @@ -313,6 +316,7 @@ namespace MediaBrowser.Api.Playback /// /// The state. /// The video codec. + /// if set to true [is HLS]. /// System.String. protected string GetVideoQualityParam(StreamState state, string videoCodec, bool isHls) { @@ -337,20 +341,17 @@ namespace MediaBrowser.Api.Playback break; } - if (!isHls) + switch (qualitySetting) { - switch (qualitySetting) - { - case EncodingQuality.HighSpeed: - param += " -crf 23"; - break; - case EncodingQuality.HighQuality: - param += " -crf 20"; - break; - case EncodingQuality.MaxQuality: - param += " -crf 18"; - break; - } + case EncodingQuality.HighSpeed: + param += " -crf 23"; + break; + case EncodingQuality.HighQuality: + param += " -crf 20"; + break; + case EncodingQuality.MaxQuality: + param += " -crf 18"; + break; } } @@ -502,14 +503,13 @@ namespace MediaBrowser.Api.Playback return string.Format("{4} -vf \"{0}scale=trunc({1}/2)*2:trunc({2}/2)*2{3}\"", yadifParam, widthParam, heightParam, assSubtitleParam, copyTsParam); } - // If Max dimensions were supplied - //this makes my brain hurt. For width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size + // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size if (request.MaxWidth.HasValue && request.MaxHeight.HasValue) { - var MaxwidthParam = request.MaxWidth.Value.ToString(UsCulture); - var MaxheightParam = request.MaxHeight.Value.ToString(UsCulture); + var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture); + var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture); - return string.Format("{4} -vf \"{0}scale=trunc(min(iw\\,{1})/2)*2:trunc(min((iw/dar)\\,{2})/2)*2{3}\"", yadifParam, MaxwidthParam, MaxheightParam, assSubtitleParam, copyTsParam); + return string.Format("{4} -vf \"{0}scale=trunc(min(iw\\,{1})/2)*2:trunc(min((iw/dar)\\,{2})/2)*2{3}\"", yadifParam, maxWidthParam, maxHeightParam, assSubtitleParam, copyTsParam); } var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase); @@ -774,29 +774,24 @@ namespace MediaBrowser.Api.Playback { var codec = request.AudioCodec; - if (!string.IsNullOrEmpty(codec)) + if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase)) - { - return "aac -strict experimental"; - } - if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) - { - return "libmp3lame"; - } - if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase)) - { - return "libvorbis"; - } - if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase)) - { - return "wmav2"; - } - - return codec.ToLower(); + return "aac -strict experimental"; + } + if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) + { + return "libmp3lame"; + } + if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase)) + { + return "libvorbis"; + } + if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase)) + { + return "wmav2"; } - return "copy"; + return codec.ToLower(); } /// @@ -972,8 +967,6 @@ namespace MediaBrowser.Api.Playback private async void StreamToStandardInput(Process process, StreamState state) { - state.StandardInputCancellationTokenSource = new CancellationTokenSource(); - try { await StreamToStandardInputInternal(process, state).ConfigureAwait(false); @@ -1034,11 +1027,6 @@ namespace MediaBrowser.Api.Playback { var hasFixedResolution = state.VideoRequest.HasFixedResolution; - if (isHls) - { - return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture)); - } - if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) { if (hasFixedResolution) @@ -1049,7 +1037,6 @@ namespace MediaBrowser.Api.Playback // With vpx when crf is used, b:v becomes a max rate // https://trac.ffmpeg.org/wiki/vpxEncodingGuide return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); - //return string.Format(" -minrate:v ({0}*.95) -maxrate:v ({0}*1.05) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture)); } if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase)) @@ -1057,13 +1044,17 @@ namespace MediaBrowser.Api.Playback return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); } - // H264 if (hasFixedResolution) { + if (isHls) + { + return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture)); + } + return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture)); } - + return string.Format(" -maxrate {0} -bufsize {1}", bitrate.Value.ToString(UsCulture), (bitrate.Value * 2).ToString(UsCulture)); @@ -1212,7 +1203,7 @@ namespace MediaBrowser.Api.Playback if (i == 0) { - // Device profile name + request.DeviceProfileId = val; } else if (i == 1) { @@ -1270,35 +1261,35 @@ namespace MediaBrowser.Api.Playback { if (videoRequest != null) { - videoRequest.MaxWidth = int.Parse(val, UsCulture); + videoRequest.MaxFramerate = double.Parse(val, UsCulture); } } else if (i == 12) { if (videoRequest != null) { - videoRequest.MaxHeight = int.Parse(val, UsCulture); + videoRequest.MaxWidth = int.Parse(val, UsCulture); } } else if (i == 13) { if (videoRequest != null) { - videoRequest.Framerate = int.Parse(val, UsCulture); + videoRequest.MaxHeight = int.Parse(val, UsCulture); } } else if (i == 14) { if (videoRequest != null) { - request.StartTimeTicks = long.Parse(val, UsCulture); + videoRequest.Framerate = int.Parse(val, UsCulture); } } else if (i == 15) { if (videoRequest != null) { - videoRequest.Profile = val; + request.StartTimeTicks = long.Parse(val, UsCulture); } } else if (i == 16) @@ -1487,9 +1478,181 @@ namespace MediaBrowser.Api.Playback state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 10; state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440; + ApplyDeviceProfileSettings(state); + return state; } + private void ApplyDeviceProfileSettings(StreamState state) + { + var headers = new Dictionary(); + + foreach (var key in Request.Headers.AllKeys) + { + headers[key] = Request.Headers[key]; + } + + var profile = string.IsNullOrWhiteSpace(state.Request.DeviceProfileId) ? + DlnaManager.GetProfile(headers) : + DlnaManager.GetProfile(state.Request.DeviceProfileId); + + if (profile == null) + { + // Don't use settings from the default profile. + // Only use a specific profile if it was requested. + return; + } + + var container = Path.GetExtension(state.RequestedUrl); + + if (string.IsNullOrEmpty(container)) + { + container = Path.GetExtension(GetOutputFilePath(state)); + } + + var audioCodec = state.Request.AudioCodec; + + if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null) + { + audioCodec = state.AudioStream.Codec; + } + + var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec; + + if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null) + { + videoCodec = state.VideoStream.Codec; + } + + var mediaProfile = state.VideoRequest == null ? + profile.GetAudioMediaProfile(container, audioCodec, state.AudioStream) : + profile.GetVideoMediaProfile(container, audioCodec, videoCodec, state.AudioStream, state.VideoStream); + + if (mediaProfile != null) + { + state.MimeType = mediaProfile.MimeType; + state.OrgPn = mediaProfile.OrgPn; + } + + var transcodingProfile = state.VideoRequest == null ? + profile.GetAudioTranscodingProfile(container, audioCodec) : + profile.GetVideoTranscodingProfile(container, audioCodec, videoCodec); + + if (transcodingProfile != null) + { + state.EstimateContentLength = transcodingProfile.EstimateContentLength; + state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; + state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; + + foreach (var setting in transcodingProfile.Settings) + { + switch (setting.Name) + { + case TranscodingSettingType.VideoProfile: + { + if (state.VideoRequest != null && string.IsNullOrWhiteSpace(state.VideoRequest.Profile)) + { + state.VideoRequest.Profile = setting.Value; + } + break; + } + default: + throw new ArgumentException("Unrecognized TranscodingSettingType"); + } + } + } + } + + + /// + /// Adds the dlna headers. + /// + /// The state. + /// The response headers. + /// if set to true [is statically streamed]. + /// true if XXXX, false otherwise + protected void AddDlnaHeaders(StreamState state, IDictionary responseHeaders, bool isStaticallyStreamed) + { + var timeSeek = GetHeader("TimeSeekRange.dlna.org"); + + if (!string.IsNullOrEmpty(timeSeek)) + { + ResultFactory.ThrowError(406, "Time seek not supported during encoding.", responseHeaders); + return; + } + + var transferMode = GetHeader("transferMode.dlna.org"); + responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode; + responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*"; + + var contentFeatures = string.Empty; + var extension = GetOutputFileExtension(state); + + // first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none + var orgOp = isStaticallyStreamed || state.TranscodeSeekInfo == TranscodeSeekInfo.Bytes ? ";DLNA.ORG_OP=01" : ";DLNA.ORG_OP=00"; + + // 0 = native, 1 = transcoded + var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1"; + + const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000"; + + if (!string.IsNullOrWhiteSpace(state.OrgPn)) + { + contentFeatures = "DLNA.ORG_PN=" + state.OrgPn; + } + else if (string.Equals(extension, ".mp3", StringComparison.OrdinalIgnoreCase)) + { + contentFeatures = "DLNA.ORG_PN=MP3"; + } + else if (string.Equals(extension, ".aac", StringComparison.OrdinalIgnoreCase)) + { + contentFeatures = "DLNA.ORG_PN=AAC_ISO"; + } + else if (string.Equals(extension, ".wma", StringComparison.OrdinalIgnoreCase)) + { + contentFeatures = "DLNA.ORG_PN=WMABASE"; + } + else if (string.Equals(extension, ".avi", StringComparison.OrdinalIgnoreCase)) + { + contentFeatures = "DLNA.ORG_PN=AVI"; + } + else if (string.Equals(extension, ".mkv", StringComparison.OrdinalIgnoreCase)) + { + contentFeatures = "DLNA.ORG_PN=MATROSKA"; + } + else if (string.Equals(extension, ".mp4", StringComparison.OrdinalIgnoreCase)) + { + contentFeatures = "DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC"; + } + else if (string.Equals(extension, ".mpeg", StringComparison.OrdinalIgnoreCase)) + { + contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL"; + } + else if (string.Equals(extension, ".ts", StringComparison.OrdinalIgnoreCase)) + { + contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL"; + } + //else if (string.Equals(extension, ".wmv", StringComparison.OrdinalIgnoreCase)) + //{ + // contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE"; + //} + //else if (string.Equals(extension, ".asf", StringComparison.OrdinalIgnoreCase)) + //{ + // // ?? + // contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE"; + //} + + if (!string.IsNullOrEmpty(contentFeatures)) + { + responseHeaders["contentFeatures.dlna.org"] = (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); + } + + foreach (var item in responseHeaders) + { + Request.Response.AddHeader(item.Key, item.Value); + } + } + /// /// Enforces the resolution limit. /// @@ -1605,7 +1768,7 @@ namespace MediaBrowser.Api.Playback return "vorbis"; } - return null; + return "copy"; } /// diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index eb8f415e0d..96b36ac7fc 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -2,13 +2,13 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using System; using System.Collections.Generic; @@ -24,7 +24,7 @@ namespace MediaBrowser.Api.Playback.Hls /// public abstract class BaseHlsService : BaseStreamingService { - protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager) + protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager) { } @@ -200,7 +200,7 @@ namespace MediaBrowser.Api.Playback.Hls builder.AppendLine("#EXTM3U"); // Pad a little to satisfy the apple hls validator - var paddedBitrate = Convert.ToInt32(bitrate * 1.05); + var paddedBitrate = Convert.ToInt32(bitrate * 1.15); // Main stream builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(UsCulture)); diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 66e8b0d149..ab0cd8871f 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -1,12 +1,12 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using ServiceStack; using System; @@ -60,7 +60,7 @@ namespace MediaBrowser.Api.Playback.Hls public class DynamicHlsService : BaseHlsService { - public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager) + public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager) { } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index a2080995d0..1bca4cae9a 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -52,7 +53,7 @@ namespace MediaBrowser.Api.Playback.Hls /// public class VideoHlsService : BaseHlsService { - public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager) + public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager) { } diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 4d8d3a5816..55b311f867 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; @@ -16,23 +17,22 @@ namespace MediaBrowser.Api.Playback.Progressive /// /// Class GetAudioStream /// - [Route("/Audio/{Id}/stream.mp3", "GET")] - [Route("/Audio/{Id}/stream.wma", "GET")] - [Route("/Audio/{Id}/stream.aac", "GET")] - [Route("/Audio/{Id}/stream.flac", "GET")] - [Route("/Audio/{Id}/stream.ogg", "GET")] - [Route("/Audio/{Id}/stream.oga", "GET")] - [Route("/Audio/{Id}/stream.webm", "GET")] - [Route("/Audio/{Id}/stream", "GET")] - [Route("/Audio/{Id}/stream.mp3", "HEAD")] - [Route("/Audio/{Id}/stream.wma", "HEAD")] - [Route("/Audio/{Id}/stream.aac", "HEAD")] - [Route("/Audio/{Id}/stream.flac", "HEAD")] - [Route("/Audio/{Id}/stream.ogg", "HEAD")] - [Route("/Audio/{Id}/stream.oga", "HEAD")] - [Route("/Audio/{Id}/stream.webm", "HEAD")] - [Route("/Audio/{Id}/stream", "HEAD")] - [Api(Description = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.mp3", "GET", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.wma", "GET", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.aac", "GET", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.flac", "GET", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.ogg", "GET", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.oga", "GET", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.webm", "GET", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream", "GET", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.mp3", "HEAD", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.wma", "HEAD", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.aac", "HEAD", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.flac", "HEAD", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.ogg", "HEAD", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.oga", "HEAD", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream.webm", "HEAD", Summary = "Gets an audio stream")] + [Route("/Audio/{Id}/stream", "HEAD", Summary = "Gets an audio stream")] public class GetAudioStream : StreamRequest { @@ -43,7 +43,8 @@ namespace MediaBrowser.Api.Playback.Progressive /// public class AudioService : BaseProgressiveStreamingService { - public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, httpClient, imageProcessor) + public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IHttpClient httpClient, IImageProcessor imageProcessor) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, httpClient, imageProcessor) { } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 9cb989fc21..b4fe9a094f 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -1,13 +1,13 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using ServiceStack.Web; using System; @@ -26,8 +26,8 @@ namespace MediaBrowser.Api.Playback.Progressive protected readonly IImageProcessor ImageProcessor; protected readonly IHttpClient HttpClient; - protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IHttpClient httpClient, IImageProcessor imageProcessor) - : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager) + protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IHttpClient httpClient, IImageProcessor imageProcessor) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager) { HttpClient = httpClient; ImageProcessor = imageProcessor; @@ -100,92 +100,6 @@ namespace MediaBrowser.Api.Playback.Progressive return null; } - /// - /// Adds the dlna headers. - /// - /// The state. - /// The response headers. - /// if set to true [is statically streamed]. - /// true if XXXX, false otherwise - private void AddDlnaHeaders(StreamState state, IDictionary responseHeaders, bool isStaticallyStreamed) - { - var timeSeek = GetHeader("TimeSeekRange.dlna.org"); - - if (!string.IsNullOrEmpty(timeSeek)) - { - ResultFactory.ThrowError(406, "Time seek not supported during encoding.", responseHeaders); - return; - } - - var transferMode = GetHeader("transferMode.dlna.org"); - responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode; - responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*"; - - var contentFeatures = string.Empty; - var extension = GetOutputFileExtension(state); - - // first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none - var orgOp = isStaticallyStreamed ? ";DLNA.ORG_OP=01" : ";DLNA.ORG_OP=00"; - - // 0 = native, 1 = transcoded - var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1"; - - const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000"; - - if (string.Equals(extension, ".mp3", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=MP3"; - } - else if (string.Equals(extension, ".aac", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=AAC_ISO"; - } - else if (string.Equals(extension, ".wma", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=WMABASE"; - } - else if (string.Equals(extension, ".avi", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=AVI"; - } - else if (string.Equals(extension, ".mkv", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=MATROSKA"; - } - else if (string.Equals(extension, ".mp4", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC"; - } - else if (string.Equals(extension, ".mpeg", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL"; - } - else if (string.Equals(extension, ".ts", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL"; - } - //else if (string.Equals(extension, ".wmv", StringComparison.OrdinalIgnoreCase)) - //{ - // contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE"; - //} - //else if (string.Equals(extension, ".asf", StringComparison.OrdinalIgnoreCase)) - //{ - // // ?? - // contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE"; - //} - - - if (!string.IsNullOrEmpty(contentFeatures)) - { - responseHeaders["contentFeatures.dlna.org"] = (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); - } - - foreach (var item in responseHeaders) - { - Request.Response.AddHeader(item.Key, item.Value); - } - } - /// /// Gets the type of the transcoding job. /// @@ -303,18 +217,30 @@ namespace MediaBrowser.Api.Playback.Progressive var contentType = state.GetMimeType(outputPath); + var contentLength = state.EstimateContentLength ? GetEstimatedContentLength(state) : null; + + if (contentLength.HasValue) + { + responseHeaders["Content-Length"] = contentLength.Value.ToString(UsCulture); + } + // Headers only if (isHeadRequest) { var streamResult = ResultFactory.GetResult(new byte[] { }, contentType, responseHeaders); - var hasOptions = streamResult as IHasOptions; - if (hasOptions != null) + + if (!contentLength.HasValue) { - if (hasOptions.Options.ContainsKey("Content-Length")) + var hasOptions = streamResult as IHasOptions; + if (hasOptions != null) { - hasOptions.Options.Remove("Content-Length"); + if (hasOptions.Options.ContainsKey("Content-Length")) + { + hasOptions.Options.Remove("Content-Length"); + } } } + return streamResult; } @@ -339,5 +265,31 @@ namespace MediaBrowser.Api.Playback.Progressive return result; } + + /// + /// Gets the length of the estimated content. + /// + /// The state. + /// System.Nullable{System.Int64}. + private long? GetEstimatedContentLength(StreamState state) + { + var totalBitrate = 0; + + if (state.Request.AudioBitRate.HasValue) + { + totalBitrate += state.Request.AudioBitRate.Value; + } + if (state.VideoRequest != null && state.VideoRequest.VideoBitRate.HasValue) + { + totalBitrate += state.VideoRequest.VideoBitRate.Value; + } + + if (totalBitrate > 0 && state.RunTimeTicks.HasValue) + { + return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds); + } + + return null; + } } } diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 43dc6f0d49..855c036911 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; @@ -58,7 +59,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// public class VideoService : BaseProgressiveStreamingService { - public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, httpClient, imageProcessor) + public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, httpClient, imageProcessor) { } @@ -137,6 +138,11 @@ namespace MediaBrowser.Api.Playback.Progressive return state.VideoStream != null && IsH264(state.VideoStream) ? args + " -bsf h264_mp4toannexb" : args; } + if (state.EnableMpegtsM2TsMode) + { + args += " -mpegts_m2ts_mode 1"; + } + const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))"; args += keyFrameArg; diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 8db5920f6b..0eb2984fb5 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -65,6 +65,9 @@ namespace MediaBrowser.Api.Playback [ApiMember(Name = "Static", Description = "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool Static { get; set; } + [ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DeviceProfileId { get; set; } + /// /// For testing purposes /// diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index ecc5c93ef2..504d7d921b 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using System.Collections.Generic; @@ -77,8 +78,21 @@ namespace MediaBrowser.Api.Playback public string InputAudioCodec { get; set; } + public string MimeType { get; set; } + public string OrgPn { get; set; } + + // DLNA Settings + public bool EstimateContentLength { get; set; } + public bool EnableMpegtsM2TsMode { get; set; } + public TranscodeSeekInfo TranscodeSeekInfo { get; set; } + public string GetMimeType(string outputPath) { + if (!string.IsNullOrEmpty(MimeType)) + { + return MimeType; + } + return MimeTypes.GetMimeType(outputPath); } } diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs index daa735fe53..80080fbb31 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs @@ -2,18 +2,17 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Model.Tasks; using ServiceStack; +using ServiceStack.Text.Controller; using System; using System.Collections.Generic; using System.Linq; -using ServiceStack.Text.Controller; namespace MediaBrowser.Api.ScheduledTasks { /// /// Class GetScheduledTask /// - [Route("/ScheduledTasks/{Id}", "GET")] - [Api(Description = "Gets a scheduled task, by Id")] + [Route("/ScheduledTasks/{Id}", "GET", Summary = "Gets a scheduled task, by Id")] public class GetScheduledTask : IReturn { /// @@ -27,8 +26,7 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Class GetScheduledTasks /// - [Route("/ScheduledTasks", "GET")] - [Api(Description = "Gets scheduled tasks")] + [Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")] public class GetScheduledTasks : IReturn> { [ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] @@ -38,8 +36,7 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Class StartScheduledTask /// - [Route("/ScheduledTasks/Running/{Id}", "POST")] - [Api(Description = "Starts a scheduled task")] + [Route("/ScheduledTasks/Running/{Id}", "POST", Summary = "Starts a scheduled task")] public class StartScheduledTask : IReturnVoid { /// @@ -53,8 +50,7 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Class StopScheduledTask /// - [Route("/ScheduledTasks/Running/{Id}", "DELETE")] - [Api(Description = "Stops a scheduled task")] + [Route("/ScheduledTasks/Running/{Id}", "DELETE", Summary = "Stops a scheduled task")] public class StopScheduledTask : IReturnVoid { /// @@ -68,8 +64,7 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Class UpdateScheduledTaskTriggers /// - [Route("/ScheduledTasks/{Id}/Triggers", "POST")] - [Api(Description = "Updates the triggers for a scheduled task")] + [Route("/ScheduledTasks/{Id}/Triggers", "POST", Summary = "Updates the triggers for a scheduled task")] public class UpdateScheduledTaskTriggers : List, IReturnVoid { /// diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index a787684bbf..c5d0a621d4 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -20,9 +20,8 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class GetItems /// - [Route("/Items", "GET")] - [Route("/Users/{UserId}/Items", "GET")] - [Api(Description = "Gets items based on a query.")] + [Route("/Items", "GET", Summary = "Gets items based on a query.")] + [Route("/Users/{UserId}/Items", "GET", Summary = "Gets items based on a query.")] public class GetItems : BaseItemsRequest, IReturn { /// diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index fc7fdd1600..7eff5054be 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -8,15 +8,13 @@ using ServiceStack; using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; namespace MediaBrowser.Api.UserLibrary { /// /// Class GetStudios /// - [Route("/Studios", "GET")] - [Api(Description = "Gets all studios from a given item, folder, or the entire library")] + [Route("/Studios", "GET", Summary = "Gets all studios from a given item, folder, or the entire library")] public class GetStudios : GetItemsByName { } @@ -24,8 +22,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class GetStudio /// - [Route("/Studios/{Name}", "GET")] - [Api(Description = "Gets a studio, by name")] + [Route("/Studios/{Name}", "GET", Summary = "Gets a studio, by name")] public class GetStudio : IReturn { /// diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index c6051c02cc..a49f957f60 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -705,7 +705,7 @@ namespace MediaBrowser.Api.UserLibrary datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); } - var session = GetSession(); + var session = GetSession(_sessionManager); var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); @@ -719,15 +719,6 @@ namespace MediaBrowser.Api.UserLibrary return dto; } - private SessionInfo GetSession() - { - var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); - - return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) && - string.Equals(i.Client, auth.Client) && - string.Equals(i.ApplicationVersion, auth.Version)); - } - /// /// Posts the specified request. /// @@ -744,7 +735,7 @@ namespace MediaBrowser.Api.UserLibrary { CanSeek = request.CanSeek, Item = item, - SessionId = GetSession().Id, + SessionId = GetSession(_sessionManager).Id, QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(), MediaSourceId = request.MediaSourceId }; @@ -768,7 +759,7 @@ namespace MediaBrowser.Api.UserLibrary PositionTicks = request.PositionTicks, IsMuted = request.IsMuted, IsPaused = request.IsPaused, - SessionId = GetSession().Id, + SessionId = GetSession(_sessionManager).Id, MediaSourceId = request.MediaSourceId }; @@ -787,7 +778,7 @@ namespace MediaBrowser.Api.UserLibrary var item = _dtoService.GetItemByDtoId(request.Id, user.Id); - var session = GetSession(); + var session = GetSession(_sessionManager); var info = new PlaybackStopInfo { @@ -817,7 +808,7 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var session = GetSession(); + var session = GetSession(_sessionManager); var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); diff --git a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs index 38139645eb..25acd613ca 100644 --- a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Api.WebSocket /// Task{SystemInfo}. protected override Task> GetDataToSend(object state) { - return Task.FromResult(_sessionManager.Sessions.Select(_dtoService.GetSessionInfoDto)); + return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_dtoService.GetSessionInfoDto)); } } } diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index 04b8865c1d..f6f800f4af 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -102,7 +102,6 @@ - diff --git a/MediaBrowser.Controller/Dlna/CodecProfile.cs b/MediaBrowser.Controller/Dlna/CodecProfile.cs index 2b9a40ea06..e2eb60d9a1 100644 --- a/MediaBrowser.Controller/Dlna/CodecProfile.cs +++ b/MediaBrowser.Controller/Dlna/CodecProfile.cs @@ -1,13 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Xml.Serialization; namespace MediaBrowser.Controller.Dlna { public class CodecProfile { + [XmlAttribute("type")] public CodecType Type { get; set; } + public ProfileCondition[] Conditions { get; set; } + + [XmlAttribute("codec")] public string Codec { get; set; } public CodecProfile() @@ -30,16 +35,23 @@ namespace MediaBrowser.Controller.Dlna public enum CodecType { - VideoCodec = 0, - VideoAudioCodec = 1, - AudioCodec = 2 + Video = 0, + VideoAudio = 1, + Audio = 2 } public class ProfileCondition { + [XmlAttribute("condition")] public ProfileConditionType Condition { get; set; } + + [XmlAttribute("property")] public ProfileConditionValue Property { get; set; } + + [XmlAttribute("value")] public string Value { get; set; } + + [XmlAttribute("isRequired")] public bool IsRequired { get; set; } public ProfileCondition() @@ -69,8 +81,6 @@ namespace MediaBrowser.Controller.Dlna VideoBitrate, VideoFramerate, VideoLevel, - VideoPacketLength, - VideoProfile, - VideoTimestamp + VideoProfile } } diff --git a/MediaBrowser.Controller/Dlna/ContainerProfile.cs b/MediaBrowser.Controller/Dlna/ContainerProfile.cs index 3bd3c9eaf6..1029ba72cb 100644 --- a/MediaBrowser.Controller/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Controller/Dlna/ContainerProfile.cs @@ -1,12 +1,16 @@ using System.Collections.Generic; using System.Linq; +using System.Xml.Serialization; namespace MediaBrowser.Controller.Dlna { public class ContainerProfile { + [XmlAttribute("type")] public DlnaProfileType Type { get; set; } public ProfileCondition[] Conditions { get; set; } + + [XmlAttribute("container")] public string Container { get; set; } public ContainerProfile() diff --git a/MediaBrowser.Controller/Dlna/DeviceIdentification.cs b/MediaBrowser.Controller/Dlna/DeviceIdentification.cs index 461c77537c..c9cd4bc703 100644 --- a/MediaBrowser.Controller/Dlna/DeviceIdentification.cs +++ b/MediaBrowser.Controller/Dlna/DeviceIdentification.cs @@ -1,4 +1,6 @@  +using System.Xml.Serialization; + namespace MediaBrowser.Controller.Dlna { public class DeviceIdentification @@ -41,9 +43,7 @@ namespace MediaBrowser.Controller.Dlna /// /// Gets or sets the manufacturer. /// - /// - /// The manufacturer. - /// + /// The manufacturer. public string Manufacturer { get; set; } /// /// Gets or sets the manufacturer URL. @@ -64,8 +64,13 @@ namespace MediaBrowser.Controller.Dlna public class HttpHeaderInfo { + [XmlAttribute("name")] public string Name { get; set; } + + [XmlAttribute("value")] public string Value { get; set; } + + [XmlAttribute("match")] public HeaderMatchType Match { get; set; } } diff --git a/MediaBrowser.Controller/Dlna/DeviceProfile.cs b/MediaBrowser.Controller/Dlna/DeviceProfile.cs index f3de1bc34a..c1fc713e4a 100644 --- a/MediaBrowser.Controller/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Controller/Dlna/DeviceProfile.cs @@ -1,6 +1,13 @@ - +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Serialization; + namespace MediaBrowser.Controller.Dlna { + [XmlRoot("Profile")] public class DeviceProfile { /// @@ -9,25 +16,11 @@ namespace MediaBrowser.Controller.Dlna /// The name. public string Name { get; set; } - /// - /// Gets or sets the type of the client. - /// - /// The type of the client. - public string ClientType { get; set; } - - /// - /// Gets or sets the transcoding profiles. - /// - /// The transcoding profiles. - public TranscodingProfile[] TranscodingProfiles { get; set; } - - /// - /// Gets or sets the direct play profiles. - /// - /// The direct play profiles. - public DirectPlayProfile[] DirectPlayProfiles { get; set; } + [XmlIgnore] + public string Id { get; set; } - public ContainerProfile[] ContainerProfiles { get; set; } + [XmlIgnore] + public DeviceProfileType ProfileType { get; set; } /// /// Gets or sets the identification. @@ -43,7 +36,9 @@ namespace MediaBrowser.Controller.Dlna public string ModelNumber { get; set; } public string ModelUrl { get; set; } public bool IgnoreTranscodeByteRangeRequests { get; set; } - public bool SupportsAlbumArtInDidl { get; set; } + public bool EnableAlbumArtInDidl { get; set; } + + public string SupportedMediaTypes { get; set; } /// /// Controls the content of the X_DLNADOC element in the urn:schemas-dlna-org:device-1-0 namespace. @@ -60,14 +55,27 @@ namespace MediaBrowser.Controller.Dlna public string ProtocolInfo { get; set; } - public MediaProfile[] MediaProfiles { get; set; } - public CodecProfile[] CodecProfiles { get; set; } - public int TimelineOffsetSeconds { get; set; } - public bool RequiresPlainVideoItems { get; set; } public bool RequiresPlainFolders { get; set; } + /// + /// Gets or sets the direct play profiles. + /// + /// The direct play profiles. + public DirectPlayProfile[] DirectPlayProfiles { get; set; } + + /// + /// Gets or sets the transcoding profiles. + /// + /// The transcoding profiles. + public TranscodingProfile[] TranscodingProfiles { get; set; } + + public ContainerProfile[] ContainerProfiles { get; set; } + + public CodecProfile[] CodecProfiles { get; set; } + public MediaProfile[] MediaProfiles { get; set; } + public DeviceProfile() { DirectPlayProfiles = new DirectPlayProfile[] { }; @@ -75,6 +83,149 @@ namespace MediaBrowser.Controller.Dlna MediaProfiles = new MediaProfile[] { }; CodecProfiles = new CodecProfile[] { }; ContainerProfiles = new ContainerProfile[] { }; + + SupportedMediaTypes = "Audio,Photo,Video"; + } + + public List GetSupportedMediaTypes() + { + return (SupportedMediaTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + + public TranscodingProfile GetAudioTranscodingProfile(string container, string audioCodec) + { + container = (container ?? string.Empty).TrimStart('.'); + + return TranscodingProfiles.FirstOrDefault(i => + { + if (i.Type != DlnaProfileType.Audio) + { + return false; + } + + if (!string.Equals(container, i.Container, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty)) + { + return false; + } + + return true; + }); + } + + public TranscodingProfile GetVideoTranscodingProfile(string container, string audioCodec, string videoCodec) + { + container = (container ?? string.Empty).TrimStart('.'); + + return TranscodingProfiles.FirstOrDefault(i => + { + if (i.Type != DlnaProfileType.Video) + { + return false; + } + + if (!string.Equals(container, i.Container, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (!i.GetAudioCodecs().Contains(audioCodec ?? string.Empty)) + { + return false; + } + + if (!string.Equals(videoCodec, i.VideoCodec, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + }); + } + + public MediaProfile GetAudioMediaProfile(string container, string audioCodec, MediaStream audioStream) + { + container = (container ?? string.Empty).TrimStart('.'); + + return MediaProfiles.FirstOrDefault(i => + { + if (i.Type != DlnaProfileType.Audio) + { + return false; + } + + var containers = i.GetContainers().ToList(); + if (containers.Count > 0 && !containers.Contains(container)) + { + return false; + } + + var audioCodecs = i.GetAudioCodecs().ToList(); + if (audioCodecs.Count > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty)) + { + return false; + } + + return true; + }); + } + + public MediaProfile GetVideoMediaProfile(string container, string audioCodec, string videoCodec, MediaStream audioStream, MediaStream videoStream) + { + container = (container ?? string.Empty).TrimStart('.'); + + return MediaProfiles.FirstOrDefault(i => + { + if (i.Type != DlnaProfileType.Video) + { + return false; + } + + var containers = i.GetContainers().ToList(); + if (containers.Count > 0 && !containers.Contains(container)) + { + return false; + } + + var audioCodecs = i.GetAudioCodecs().ToList(); + if (audioCodecs.Count > 0 && !audioCodecs.Contains(audioCodec ?? string.Empty)) + { + return false; + } + + var videoCodecs = i.GetVideoCodecs().ToList(); + if (videoCodecs.Count > 0 && !videoCodecs.Contains(videoCodec ?? string.Empty)) + { + return false; + } + + return true; + }); + } + + public MediaProfile GetPhotoMediaProfile(string container) + { + container = (container ?? string.Empty).TrimStart('.'); + + return MediaProfiles.FirstOrDefault(i => + { + if (i.Type != DlnaProfileType.Photo) + { + return false; + } + + var containers = i.GetContainers().ToList(); + if (containers.Count > 0 && !containers.Contains(container)) + { + return false; + } + + return true; + }); } } } diff --git a/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs b/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs index 686b31287e..ad70640daa 100644 --- a/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Controller/Dlna/DirectPlayProfile.cs @@ -1,14 +1,21 @@ using System.Collections.Generic; using System.Linq; +using System.Xml.Serialization; namespace MediaBrowser.Controller.Dlna { public class DirectPlayProfile { + [XmlAttribute("container")] public string Container { get; set; } + + [XmlAttribute("audioCodec")] public string AudioCodec { get; set; } + + [XmlAttribute("videoCodec")] public string VideoCodec { get; set; } + [XmlAttribute("type")] public DlnaProfileType Type { get; set; } public List GetContainers() diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index 6de17e5511..521d17e01f 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -1,21 +1,54 @@ -using System.Collections.Generic; +using MediaBrowser.Model.Dlna; +using System.Collections.Generic; namespace MediaBrowser.Controller.Dlna { public interface IDlnaManager { /// - /// Gets the dlna profiles. + /// Gets the profile infos. /// - /// IEnumerable{DlnaProfile}. - IEnumerable GetProfiles(); + /// IEnumerable{DeviceProfileInfo}. + IEnumerable GetProfileInfos(); + + /// + /// Gets the profile. + /// + /// The headers. + /// DeviceProfile. + DeviceProfile GetProfile(IDictionary headers); /// /// Gets the default profile. /// - /// DlnaProfile. + /// DeviceProfile. DeviceProfile GetDefaultProfile(); + /// + /// Creates the profile. + /// + /// The profile. + void CreateProfile(DeviceProfile profile); + + /// + /// Updates the profile. + /// + /// The profile. + void UpdateProfile(DeviceProfile profile); + + /// + /// Deletes the profile. + /// + /// The identifier. + void DeleteProfile(string id); + + /// + /// Gets the profile. + /// + /// The identifier. + /// DeviceProfile. + DeviceProfile GetProfile(string id); + /// /// Gets the profile. /// diff --git a/MediaBrowser.Controller/Dlna/MediaProfile.cs b/MediaBrowser.Controller/Dlna/MediaProfile.cs index 1d2613face..bf3057294c 100644 --- a/MediaBrowser.Controller/Dlna/MediaProfile.cs +++ b/MediaBrowser.Controller/Dlna/MediaProfile.cs @@ -1,16 +1,27 @@ using System.Collections.Generic; using System.Linq; +using System.Xml.Serialization; namespace MediaBrowser.Controller.Dlna { public class MediaProfile { + [XmlAttribute("container")] public string Container { get; set; } + + [XmlAttribute("audioCodec")] public string AudioCodec { get; set; } + + [XmlAttribute("videoCodec")] public string VideoCodec { get; set; } + [XmlAttribute("type")] public DlnaProfileType Type { get; set; } + + [XmlAttribute("orgPn")] public string OrgPn { get; set; } + + [XmlAttribute("mimeType")] public string MimeType { get; set; } public ProfileCondition[] Conditions { get; set; } @@ -19,6 +30,11 @@ namespace MediaBrowser.Controller.Dlna { Conditions = new ProfileCondition[] {}; } + + public List GetContainers() + { + return (Container ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } public List GetAudioCodecs() { diff --git a/MediaBrowser.Controller/Dlna/TranscodingProfile.cs b/MediaBrowser.Controller/Dlna/TranscodingProfile.cs index 007cb632e4..707f0c5731 100644 --- a/MediaBrowser.Controller/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Controller/Dlna/TranscodingProfile.cs @@ -1,17 +1,33 @@ - +using System.Collections.Generic; +using System.Linq; +using System.Xml.Serialization; + namespace MediaBrowser.Controller.Dlna { public class TranscodingProfile { + [XmlAttribute("container")] public string Container { get; set; } + [XmlAttribute("type")] public DlnaProfileType Type { get; set; } + [XmlAttribute("videoCodec")] public string VideoCodec { get; set; } + + [XmlAttribute("audioCodec")] public string AudioCodec { get; set; } + [XmlAttribute("protocol")] + public string Protocol { get; set; } + + [XmlAttribute("estimateContentLength")] public bool EstimateContentLength { get; set; } + [XmlAttribute("enableMpegtsM2TsMode")] + public bool EnableMpegtsM2TsMode { get; set; } + + [XmlAttribute("transcodeSeekInfo")] public TranscodeSeekInfo TranscodeSeekInfo { get; set; } public TranscodingSetting[] Settings { get; set; } @@ -21,12 +37,19 @@ namespace MediaBrowser.Controller.Dlna Settings = new TranscodingSetting[] { }; } - public bool EnableMpegtsM2TsMode { get; set; } + + public List GetAudioCodecs() + { + return (AudioCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } } public class TranscodingSetting { + [XmlAttribute("name")] public TranscodingSettingType Name { get; set; } + + [XmlAttribute("value")] public string Value { get; set; } } diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index b3d73dc34a..53ec030a78 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -3,8 +3,9 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Globalization; using System.Linq; +using System.Runtime.Serialization; namespace MediaBrowser.Controller.Entities { @@ -22,7 +23,7 @@ namespace MediaBrowser.Controller.Entities /// /// The preferred metadata country code. public string PreferredMetadataCountryCode { get; set; } - + public Trailer() { RemoteTrailers = new List(); @@ -33,19 +34,19 @@ namespace MediaBrowser.Controller.Entities } public float? Metascore { get; set; } - + public List LocalTrailerIds { get; set; } - + public List RemoteTrailers { get; set; } public List Keywords { get; set; } - + /// /// Gets or sets the taglines. /// /// The taglines. public List Taglines { get; set; } - + /// /// Gets or sets the budget. /// @@ -92,6 +93,12 @@ namespace MediaBrowser.Controller.Entities { key = key + "-trailer"; + // Make sure different trailers have their own data. + if (RunTimeTicks.HasValue) + { + key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture); + } + return key; } diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index e6a62c1812..0a34b80168 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -121,10 +121,7 @@ namespace MediaBrowser.Controller.Entities { _configuration = value; - if (value == null) - { - _configurationInitialized = false; - } + _configurationInitialized = value != null; } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5e6297d060..9915ac0441 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -157,6 +157,7 @@ + @@ -187,6 +188,7 @@ + diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 119688fa70..e9081fe8a3 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -24,16 +24,23 @@ namespace MediaBrowser.Controller.MediaEncoding string Version { get; } /// - /// Extracts the image. + /// Extracts the audio image. + /// + /// The path. + /// The cancellation token. + /// Task{Stream}. + Task ExtractAudioImage(string path, CancellationToken cancellationToken); + + /// + /// Extracts the video image. /// /// The input files. /// The type. - /// if set to true [is audio]. /// The threed format. /// The offset. /// The cancellation token. /// Task{Stream}. - Task ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken); + Task ExtractVideoImage(string[] inputFiles, InputType type, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken); /// /// Extracts the text subtitle. @@ -81,6 +88,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// The type. /// System.String. string GetInputArgument(string[] inputFiles, InputType type); + + /// + /// Encodes the image. + /// + /// The options. + /// The cancellation token. + /// Task{Stream}. + Task EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken); } /// diff --git a/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs new file mode 100644 index 0000000000..a8d1e5a0f1 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/ImageEncodingOptions.cs @@ -0,0 +1,20 @@ + +namespace MediaBrowser.Controller.MediaEncoding +{ + public class ImageEncodingOptions + { + public string InputPath { get; set; } + + public int? Width { get; set; } + + public int? Height { get; set; } + + public int? MaxWidth { get; set; } + + public int? MaxHeight { get; set; } + + public int? Quality { get; set; } + + public string Format { get; set; } + } +} diff --git a/MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs b/MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs index e113521ecf..39d1c32202 100644 --- a/MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs +++ b/MediaBrowser.Controller/MediaEncoding/InternalMediaInfoResult.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Model.Entities; -using System.Collections.Generic; +using System.Collections.Generic; namespace MediaBrowser.Controller.MediaEncoding { @@ -24,7 +23,18 @@ namespace MediaBrowser.Controller.MediaEncoding /// Gets or sets the chapters. /// /// The chapters. - public List Chapters { get; set; } + public MediaChapter[] Chapters { get; set; } + } + + public class MediaChapter + { + public int id { get; set; } + public string time_base { get; set; } + public long start { get; set; } + public string start_time { get; set; } + public long end { get; set; } + public string end_time { get; set; } + public Dictionary tags { get; set; } } /// diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index 1840331773..fd1f651017 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -154,7 +154,8 @@ namespace MediaBrowser.Controller.MediaEncoding Codec = streamInfo.codec_name, Profile = streamInfo.profile, Level = streamInfo.level, - Index = streamInfo.index + Index = streamInfo.index, + PixelFormat = streamInfo.pix_fmt }; if (streamInfo.tags != null) @@ -196,24 +197,21 @@ namespace MediaBrowser.Controller.MediaEncoding } // Get stream bitrate - if (stream.Type != MediaStreamType.Subtitle) - { - var bitrate = 0; + var bitrate = 0; - if (!string.IsNullOrEmpty(streamInfo.bit_rate)) - { - bitrate = int.Parse(streamInfo.bit_rate, UsCulture); - } - else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate)) - { - // If the stream info doesn't have a bitrate get the value from the media format info - bitrate = int.Parse(formatInfo.bit_rate, UsCulture); - } + if (!string.IsNullOrEmpty(streamInfo.bit_rate)) + { + bitrate = int.Parse(streamInfo.bit_rate, UsCulture); + } + else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate) && stream.Type == MediaStreamType.Video) + { + // If the stream info doesn't have a bitrate get the value from the media format info + bitrate = int.Parse(formatInfo.bit_rate, UsCulture); + } - if (bitrate > 0) - { - stream.BitRate = bitrate; - } + if (bitrate > 0) + { + stream.BitRate = bitrate; } if (streamInfo.disposition != null) diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index d71c7af323..70b49efece 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -1,5 +1,4 @@ using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; @@ -284,22 +283,6 @@ namespace MediaBrowser.Controller.Providers break; } - case "TagLine": - { - var tagline = reader.ReadElementContentAsString(); - - var hasTaglines = item as IHasTaglines; - if (hasTaglines != null) - { - if (!string.IsNullOrWhiteSpace(tagline)) - { - hasTaglines.AddTagline(tagline); - } - } - - break; - } - case "Language": { var val = reader.ReadElementContentAsString(); @@ -380,9 +363,7 @@ namespace MediaBrowser.Controller.Providers } case "ContentRating": - case "certification": case "MPAARating": - case "ESRBRating": { var rating = reader.ReadElementContentAsString(); @@ -415,7 +396,6 @@ namespace MediaBrowser.Controller.Providers break; } - case "Runtime": case "RunningTime": { var text = reader.ReadElementContentAsString(); @@ -431,19 +411,6 @@ namespace MediaBrowser.Controller.Providers break; } - case "Genre": - { - foreach (var name in SplitNames(reader.ReadElementContentAsString())) - { - if (string.IsNullOrWhiteSpace(name)) - { - continue; - } - item.AddGenre(name); - } - break; - } - case "AspectRatio": { var val = reader.ReadElementContentAsString(); @@ -587,7 +554,6 @@ namespace MediaBrowser.Controller.Providers break; } - case "ReleaseYear": case "ProductionYear": { var val = reader.ReadElementContentAsString(); @@ -606,7 +572,6 @@ namespace MediaBrowser.Controller.Providers case "Rating": case "IMDBrating": - case "TGDBRating": { var rating = reader.ReadElementContentAsString(); @@ -683,22 +648,6 @@ namespace MediaBrowser.Controller.Providers } break; } - case "MusicbrainzId": - { - var mbz = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(mbz)) - { - if (item is MusicAlbum) - { - item.SetProviderId(MetadataProviders.MusicBrainzAlbum, mbz); - } - else if (item is MusicArtist) - { - item.SetProviderId(MetadataProviders.MusicBrainzArtist, mbz); - } - } - break; - } case "MusicBrainzAlbumId": { var mbz = reader.ReadElementContentAsString(); @@ -802,9 +751,7 @@ namespace MediaBrowser.Controller.Providers } break; - case "IMDB_ID": case "IMDB": - case "IMDbId": var imDbId = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(imDbId)) { @@ -856,15 +803,6 @@ namespace MediaBrowser.Controller.Providers break; } - case "ParentalRating": - { - using (var subtree = reader.ReadSubtree()) - { - FetchFromParentalRatingNode(subtree, item); - } - break; - } - case "Studios": { using (var subtree = reader.ReadSubtree()) @@ -1227,32 +1165,6 @@ namespace MediaBrowser.Controller.Providers } } - /// - /// Fetches from parental rating node. - /// - /// The reader. - /// The item. - private void FetchFromParentalRatingNode(XmlReader reader, T item) - { - reader.MoveToContent(); - - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - // Removed support for "Value" tag as it conflicted with MPAA rating but leaving this function for possible - // future support of "Description" -ebr - - default: - reader.Skip(); - break; - } - } - } - } - /// /// Gets the persons from XML node. /// diff --git a/MediaBrowser.Controller/Providers/ISeriesOrderManager.cs b/MediaBrowser.Controller/Providers/ISeriesOrderManager.cs new file mode 100644 index 0000000000..a3adab1b90 --- /dev/null +++ b/MediaBrowser.Controller/Providers/ISeriesOrderManager.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Common; + +namespace MediaBrowser.Controller.Providers +{ + public interface ISeriesOrderProvider + { + string OrderType { get; } + Task FindSeriesIndex(string seriesName); + } + + public static class SeriesOrderTypes + { + public const string Anime = "Anime"; + } + + public interface ISeriesOrderManager + { + Task FindSeriesIndex(string orderType, string seriesName); + void AddParts(IEnumerable orderProviders); + } +} diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index 21206af757..02cc875bd0 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -59,6 +59,14 @@ namespace MediaBrowser.Controller.Session /// Task. Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken); + /// + /// Sends the generic command. + /// + /// The command. + /// The cancellation token. + /// Task. + Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken); + /// /// Sends the library update info. /// diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index c6da865cd7..ec9ecb9ef2 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -1,9 +1,14 @@ using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Dlna; using MediaBrowser.Dlna.Profiles; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -11,21 +16,36 @@ namespace MediaBrowser.Dlna { public class DlnaManager : IDlnaManager { - private IApplicationPaths _appPaths; + private readonly IApplicationPaths _appPaths; private readonly IXmlSerializer _xmlSerializer; private readonly IFileSystem _fileSystem; - private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; - public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IJsonSerializer jsonSerializer) + public DlnaManager(IXmlSerializer xmlSerializer, IFileSystem fileSystem, IApplicationPaths appPaths, ILogger logger) { _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; - _jsonSerializer = jsonSerializer; + _appPaths = appPaths; + _logger = logger; - GetProfiles(); + //DumpProfiles(); } public IEnumerable GetProfiles() + { + ExtractProfilesIfNeeded(); + + var list = GetProfiles(UserProfilesPath, DeviceProfileType.User) + .OrderBy(i => i.Name) + .ToList(); + + list.AddRange(GetProfiles(SystemProfilesPath, DeviceProfileType.System) + .OrderBy(i => i.Name)); + + return list; + } + + private void DumpProfiles() { var list = new List { @@ -43,27 +63,72 @@ namespace MediaBrowser.Dlna new WdtvLiveProfile(), new DenonAvrProfile(), new LinksysDMA2100Profile(), - new LgTvProfile() + new LgTvProfile(), + new Foobar2000Profile(), + new DefaultProfile() }; foreach (var item in list) { - //_xmlSerializer.SerializeToFile(item, "d:\\" + _fileSystem.GetValidFilename(item.Name) + ".xml"); - //_jsonSerializer.SerializeToFile(item, "d:\\" + _fileSystem.GetValidFilename(item.Name) + ".json"); + var path = Path.Combine(_appPaths.ProgramDataPath, _fileSystem.GetValidFilename(item.Name) + ".xml"); + + _xmlSerializer.SerializeToFile(item, path); } + } - return list; + private bool _extracted; + private readonly object _syncLock = new object(); + private void ExtractProfilesIfNeeded() + { + if (!_extracted) + { + lock (_syncLock) + { + if (!_extracted) + { + try + { + ExtractSystemProfiles(); + } + catch (Exception ex) + { + _logger.ErrorException("Error extracting DLNA profiles.", ex); + } + + _extracted = true; + } + + } + } } public DeviceProfile GetDefaultProfile() { + ExtractProfilesIfNeeded(); + return new DefaultProfile(); } public DeviceProfile GetProfile(DeviceIdentification deviceInfo) { - return GetProfiles().FirstOrDefault(i => IsMatch(deviceInfo, i.Identification)) ?? - GetDefaultProfile(); + if (deviceInfo == null) + { + throw new ArgumentNullException("deviceInfo"); + } + + var profile = GetProfiles() + .FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification)); + + if (profile != null) + { + _logger.Debug("Found matching device profile: {0}", profile.Name); + } + else + { + _logger.Debug("No matching device profile found. The default will need to be used."); + } + + return profile; } private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo) @@ -124,5 +189,208 @@ namespace MediaBrowser.Dlna return true; } + + public DeviceProfile GetProfile(IDictionary headers) + { + if (headers == null) + { + throw new ArgumentNullException("headers"); + } + + return GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification)); + } + + private bool IsMatch(IDictionary headers, DeviceIdentification profileInfo) + { + return profileInfo.Headers.Any(i => IsMatch(headers, i)); + } + + private bool IsMatch(IDictionary headers, HttpHeaderInfo header) + { + string value; + + if (headers.TryGetValue(header.Name, out value)) + { + switch (header.Match) + { + case HeaderMatchType.Equals: + return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase); + case HeaderMatchType.Substring: + return value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; + case HeaderMatchType.Regex: + return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase); + default: + throw new ArgumentException("Unrecognized HeaderMatchType"); + } + } + + return false; + } + + private string UserProfilesPath + { + get + { + return Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user"); + } + } + + private string SystemProfilesPath + { + get + { + return Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system"); + } + } + + private IEnumerable GetProfiles(string path, DeviceProfileType type) + { + try + { + return new DirectoryInfo(path) + .EnumerateFiles("*", SearchOption.TopDirectoryOnly) + .Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase)) + .Select(i => ParseProfileXmlFile(i.FullName, type)) + .Where(i => i != null) + .ToList(); + } + catch (DirectoryNotFoundException) + { + return new List(); + } + } + + private DeviceProfile ParseProfileXmlFile(string path, DeviceProfileType type) + { + try + { + var profile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path); + + profile.Id = path.ToLower().GetMD5().ToString("N"); + profile.ProfileType = type; + + return profile; + } + catch (Exception ex) + { + _logger.ErrorException("Error parsing profile xml: {0}", ex, path); + + return null; + } + } + + public DeviceProfile GetProfile(string id) + { + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException("id"); + } + + var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id)); + + return ParseProfileXmlFile(info.Path, info.Info.Type); + } + + private IEnumerable GetProfileInfosInternal() + { + ExtractProfilesIfNeeded(); + + return GetProfileInfos(UserProfilesPath, DeviceProfileType.User) + .Concat(GetProfileInfos(SystemProfilesPath, DeviceProfileType.System)) + .OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1) + .ThenBy(i => i.Info.Name); + } + + public IEnumerable GetProfileInfos() + { + return GetProfileInfosInternal().Select(i => i.Info); + } + + private IEnumerable GetProfileInfos(string path, DeviceProfileType type) + { + try + { + return new DirectoryInfo(path) + .EnumerateFiles("*", SearchOption.TopDirectoryOnly) + .Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase)) + .Select(i => new InternalProfileInfo + { + Path = i.FullName, + + Info = new DeviceProfileInfo + { + Id = i.FullName.ToLower().GetMD5().ToString("N"), + Name = Path.GetFileNameWithoutExtension(i.FullName), + Type = type + } + }) + .ToList(); + } + catch (DirectoryNotFoundException) + { + return new List(); + } + } + + private void ExtractSystemProfiles() + { + var assembly = GetType().Assembly; + var namespaceName = GetType().Namespace + ".Profiles.Xml."; + + var systemProfilesPath = SystemProfilesPath; + + foreach (var name in assembly.GetManifestResourceNames() + .Where(i => i.StartsWith(namespaceName)) + .ToList()) + { + var filename = Path.GetFileName(name).Substring(namespaceName.Length); + + var path = Path.Combine(systemProfilesPath, filename); + + using (var stream = assembly.GetManifestResourceStream(name)) + { + var fileInfo = new FileInfo(path); + + if (!fileInfo.Exists || fileInfo.Length != stream.Length) + { + Directory.CreateDirectory(systemProfilesPath); + + using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + stream.CopyTo(fileStream); + } + } + } + } + + // Not necessary, but just to make it easy to find + Directory.CreateDirectory(UserProfilesPath); + } + + public void DeleteProfile(string id) + { + var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id)); + + if (info.Info.Type == DeviceProfileType.System) + { + throw new ArgumentException("System profiles cannot be deleted."); + } + + File.Delete(info.Path); + } + + public void CreateProfile(DeviceProfile profile) + { + } + + public void UpdateProfile(DeviceProfile profile) + { + } + + class InternalProfileInfo + { + internal DeviceProfileInfo Info { get; set; } + internal string Path { get; set; } + } } } \ No newline at end of file diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index bea281b614..df1fed12f0 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -61,7 +61,6 @@ - Code @@ -70,7 +69,8 @@ - + + @@ -100,7 +100,6 @@ - @@ -118,7 +117,27 @@ MediaBrowser.Model - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..6616e46acc --- /dev/null +++ b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MediaBrowser.MediaEncoding")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MediaBrowser.MediaEncoding")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("05f49ab9-2a90-4332-9d41-7817a9cccd90")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MediaBrowser.MediaEncoding/packages.config b/MediaBrowser.MediaEncoding/packages.config new file mode 100644 index 0000000000..6e52b72b8f --- /dev/null +++ b/MediaBrowser.MediaEncoding/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 960b0f6358..6c4d9d9e29 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -101,6 +101,9 @@ Configuration\UserConfiguration.cs + + Dlna\DeviceProfileInfo.cs + Drawing\DrawingUtils.cs @@ -425,6 +428,9 @@ Session\BrowseRequest.cs + + Session\GenericCommand.cs + Session\MessageCommand.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index b010ad9c99..b39cecc61d 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -88,6 +88,9 @@ Configuration\UserConfiguration.cs + + Dlna\DeviceProfileInfo.cs + Drawing\DrawingUtils.cs @@ -412,6 +415,9 @@ Session\BrowseRequest.cs + + Session\GenericCommand.cs + Session\MessageCommand.cs diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 8de54f34a9..28c5822e9a 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -589,6 +589,14 @@ namespace MediaBrowser.Model.ApiClient /// request Task SendPlayCommandAsync(string sessionId, PlayRequest request); + /// + /// Sends the command asynchronous. + /// + /// The session identifier. + /// The request. + /// Task. + Task SendCommandAsync(string sessionId, GenericCommand request); + /// /// Sends a system command to the client /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 1f304112f5..d0caa3ad20 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -57,6 +57,12 @@ namespace MediaBrowser.Model.Configuration /// The item by name path. public string ItemsByNamePath { get; set; } + /// + /// Gets or sets the metadata path. + /// + /// The metadata path. + public string MetadataPath { get; set; } + /// /// Gets or sets the display name of the season zero. /// diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 2145860c73..10f984f88e 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -52,6 +52,7 @@ namespace MediaBrowser.Model.Configuration public bool EnableLiveTvAccess { get; set; } public bool EnableMediaPlayback { get; set; } + public bool EnableContentDeletion { get; set; } public string[] BlockedMediaFolders { get; set; } @@ -63,8 +64,9 @@ namespace MediaBrowser.Model.Configuration public UserConfiguration() { IsAdministrator = true; - EnableRemoteControlOfOtherUsers = true; + EnableRemoteControlOfOtherUsers = true; + EnableContentDeletion = true; EnableLiveTvManagement = true; EnableMediaPlayback = true; EnableLiveTvAccess = true; diff --git a/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs b/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs new file mode 100644 index 0000000000..ceb27386c1 --- /dev/null +++ b/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs @@ -0,0 +1,30 @@ + +namespace MediaBrowser.Model.Dlna +{ + public class DeviceProfileInfo + { + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the type. + /// + /// The type. + public DeviceProfileType Type { get; set; } + } + + public enum DeviceProfileType + { + System = 0, + User = 1 + } +} diff --git a/MediaBrowser.Model/Drawing/ImageOutputFormat.cs b/MediaBrowser.Model/Drawing/ImageOutputFormat.cs index 6cbe75a7a0..824970073a 100644 --- a/MediaBrowser.Model/Drawing/ImageOutputFormat.cs +++ b/MediaBrowser.Model/Drawing/ImageOutputFormat.cs @@ -25,6 +25,7 @@ namespace MediaBrowser.Model.Drawing /// /// The PNG /// - Png + Png, + Webp } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index a8f751c104..b644661f49 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -123,6 +123,12 @@ namespace MediaBrowser.Model.Entities /// The filename. public string Path { get; set; } + /// + /// Gets or sets the pixel format. + /// + /// The pixel format. + public string PixelFormat { get; set; } + /// /// Gets or sets the level. /// diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 5e9b97939a..207543fe88 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -66,6 +66,7 @@ + @@ -131,6 +132,7 @@ + diff --git a/MediaBrowser.Model/Session/GenericCommand.cs b/MediaBrowser.Model/Session/GenericCommand.cs new file mode 100644 index 0000000000..f7ea0a84a5 --- /dev/null +++ b/MediaBrowser.Model/Session/GenericCommand.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Model.Session +{ + public class GenericCommand + { + public string Name { get; set; } + + public string ControllingUserId { get; set; } + + public Dictionary Arguments { get; set; } + + public GenericCommand() + { + Arguments = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + } + + /// + /// This exists simply to identify a set of known commands. + /// + public enum CoreGenericCommand + { + MoveUp = 0, + MoveDown = 1, + MoveLeft = 2, + MoveRight = 3, + PageUp = 4, + PageDown = 5, + PreviousLetter = 6, + NextLetter = 7, + ToggleOsd = 8, + ToggleContextMenu = 9, + Select = 10, + Back = 11, + TakeScreenshot = 12, + SendKey = 13, + SendString = 14, + GoHome = 15, + GoToSettings = 16, + VolumeUp = 17, + VolumeDown = 18, + Mute = 19, + Unmute = 20, + ToggleMute = 21 + } +} diff --git a/MediaBrowser.Model/Session/PlaystateCommand.cs b/MediaBrowser.Model/Session/PlaystateCommand.cs index d83c6dae54..91572ba623 100644 --- a/MediaBrowser.Model/Session/PlaystateCommand.cs +++ b/MediaBrowser.Model/Session/PlaystateCommand.cs @@ -33,7 +33,15 @@ namespace MediaBrowser.Model.Session /// /// The fullscreen /// - Fullscreen + Fullscreen, + /// + /// The rewind + /// + Rewind, + /// + /// The fast forward + /// + FastForward } public class PlaystateRequest diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs index 29032ca298..cf7904e5cb 100644 --- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs +++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs @@ -172,7 +172,7 @@ namespace MediaBrowser.Providers.BoxSets if (!string.IsNullOrEmpty(language)) { // If preferred language isn't english, get those images too - if (imageLanguages.Contains(language, StringComparer.OrdinalIgnoreCase)) + if (!imageLanguages.Contains(language, StringComparer.OrdinalIgnoreCase)) { imageLanguages.Add(language); } diff --git a/MediaBrowser.Providers/Manager/SeriesOrderManager.cs b/MediaBrowser.Providers/Manager/SeriesOrderManager.cs new file mode 100644 index 0000000000..39175d1f94 --- /dev/null +++ b/MediaBrowser.Providers/Manager/SeriesOrderManager.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common; +using MediaBrowser.Controller.Providers; + +namespace MediaBrowser.Providers.Manager +{ + public class SeriesOrderManager : ISeriesOrderManager + { + private Dictionary _providers; + + public void AddParts(IEnumerable orderProviders) + { + _providers = orderProviders + .GroupBy(p => p.OrderType) + .ToDictionary(g => g.Key, g => g.ToArray()); + } + + public async Task FindSeriesIndex(string orderType, string seriesName) + { + ISeriesOrderProvider[] providers; + if (!_providers.TryGetValue(orderType, out providers)) + return null; + + foreach (ISeriesOrderProvider provider in providers) + { + int? index = await provider.FindSeriesIndex(seriesName); + if (index != null) + return index; + } + + return null; + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index f347e72290..ff39ec70d7 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -103,6 +103,7 @@ + diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index b3c3f278eb..b2ca97f55c 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Providers.MediaInfo { Directory.CreateDirectory(Path.GetDirectoryName(path)); - using (var stream = await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, cancellationToken).ConfigureAwait(false)) + using (var stream = await _mediaEncoder.ExtractAudioImage(item.Path, cancellationToken).ConfigureAwait(false)) { using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index 044973064e..75a9d9c36a 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -50,12 +50,16 @@ namespace MediaBrowser.Providers.MediaInfo return ItemUpdateType.MetadataImport; } + private const string SchemaVersion = "1"; + private async Task GetMediaInfo(BaseItem item, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var idString = item.Id.ToString("N"); - var cachePath = Path.Combine(_appPaths.CachePath, "ffprobe-audio", idString.Substring(0, 2), idString, "v" + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json"); + var cachePath = Path.Combine(_appPaths.CachePath, + "ffprobe-audio", + idString.Substring(0, 2), idString, "v" + SchemaVersion + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json"); try { @@ -148,7 +152,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!string.IsNullOrWhiteSpace(composer)) { - foreach (var person in Split(composer)) + foreach (var person in Split(composer, false)) { audio.AddPerson(new PersonInfo { Name = person, Type = PersonType.Composer }); } @@ -221,12 +225,15 @@ namespace MediaBrowser.Providers.MediaInfo /// Splits the specified val. /// /// The val. + /// if set to true [allow comma delimiter]. /// System.String[][]. - private IEnumerable Split(string val) + private IEnumerable Split(string val, bool allowCommaDelimiter) { // Only use the comma as a delimeter if there are no slashes or pipes. // We want to be careful not to split names that have commas in them - var delimeter = _nameDelimiters.Any(i => val.IndexOf(i) != -1) ? _nameDelimiters : new[] { ',' }; + var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ? + _nameDelimiters : + new[] { ',' }; return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) @@ -312,7 +319,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!string.IsNullOrEmpty(val)) { // Sometimes the artist name is listed here, account for that - var studios = Split(val).Where(i => !audio.HasArtist(i)); + var studios = Split(val, true).Where(i => !audio.HasArtist(i)); foreach (var studio in studios) { @@ -334,7 +341,7 @@ namespace MediaBrowser.Providers.MediaInfo { audio.Genres.Clear(); - foreach (var genre in Split(val)) + foreach (var genre in Split(val, true)) { audio.AddGenre(genre); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index d516a8221f..cb326c5ad0 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -107,14 +107,16 @@ namespace MediaBrowser.Providers.MediaInfo return ItemUpdateType.MetadataImport; } + private const string SchemaVersion = "1"; + private async Task GetMediaInfo(BaseItem item, IIsoMount isoMount, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - cancellationToken.ThrowIfCancellationRequested(); - var idString = item.Id.ToString("N"); - var cachePath = Path.Combine(_appPaths.CachePath, "ffprobe-video", idString.Substring(0, 2), idString, "v" + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json"); + var cachePath = Path.Combine(_appPaths.CachePath, + "ffprobe-video", + idString.Substring(0, 2), idString, "v" + SchemaVersion + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json"); try { @@ -161,7 +163,8 @@ namespace MediaBrowser.Providers.MediaInfo var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams; - var chapters = data.Chapters ?? new List(); + var mediaChapters = (data.Chapters ?? new MediaChapter[] { }).ToList(); + var chapters = mediaChapters.Select(GetChapterInfo).ToList(); if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay)) { @@ -200,6 +203,24 @@ namespace MediaBrowser.Providers.MediaInfo await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false); } + private ChapterInfo GetChapterInfo(MediaChapter chapter) + { + var info = new ChapterInfo(); + + if (chapter.tags != null) + { + string name; + if (chapter.tags.TryGetValue("title", out name)) + { + info.Name = name; + } + } + + info.StartPositionTicks = chapter.start/100; + + return info; + } + private void FetchBdInfo(BaseItem item, List chapters, List mediaStreams, BlurayDiscInfo blurayInfo) { var video = (Video)item; diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 3547584979..70daa3f519 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -93,7 +93,7 @@ namespace MediaBrowser.Providers.MediaInfo var inputPath = MediaEncoderHelpers.GetInputArgument(item.Path, item.LocationType == LocationType.Remote, item.VideoType, item.IsoType, isoMount, item.PlayableStreamFileNames, out type); - var stream = await _mediaEncoder.ExtractImage(inputPath, type, false, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); + var stream = await _mediaEncoder.ExtractVideoImage(inputPath, type, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); return new DynamicImageResponse { diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 05f2d485e6..7a63713f19 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -295,7 +295,7 @@ namespace MediaBrowser.Providers.Movies if (!string.IsNullOrEmpty(language)) { // If preferred language isn't english, get those images too - if (imageLanguages.Contains(language, StringComparer.OrdinalIgnoreCase)) + if (!imageLanguages.Contains(language, StringComparer.OrdinalIgnoreCase)) { imageLanguages.Add(language); } diff --git a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs index a50e5f9d53..95169aef0c 100644 --- a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs @@ -130,30 +130,6 @@ namespace MediaBrowser.Providers.Music } } - /// - /// Determines whether the specified text has diacritics. - /// - /// The text. - /// true if the specified text has diacritics; otherwise, false. - private bool HasDiacritics(string text) - { - return !String.Equals(text, RemoveDiacritics(text), StringComparison.Ordinal); - } - - /// - /// Removes the diacritics. - /// - /// The text. - /// System.String. - private string RemoveDiacritics(string text) - { - return String.Concat( - text.Normalize(NormalizationForm.FormD) - .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != - UnicodeCategory.NonSpacingMark) - ).Normalize(NormalizationForm.FormC); - } - /// /// Encodes an URL. /// diff --git a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs index 5957938543..9a31fd0912 100644 --- a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -75,16 +74,6 @@ namespace MediaBrowser.Providers.Savers XmlSaverHelpers.AddCommonNodes(video, builder); - if (video.CommunityRating.HasValue) - { - builder.Append("" + SecurityElement.Escape(video.CommunityRating.Value.ToString(UsCulture)) + ""); - } - - if (!string.IsNullOrEmpty(video.Overview)) - { - builder.Append(""); - } - var musicVideo = item as MusicVideo; if (musicVideo != null) @@ -117,8 +106,12 @@ namespace MediaBrowser.Providers.Savers XmlSaverHelpers.Save(builder, xmlFilePath, new List { + // Deprecated. No longer saving in this field. "IMDBrating", + + // Deprecated. No longer saving in this field. "Description", + "Artist", "Album", "TmdbCollectionName" diff --git a/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs b/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs index e69f2e0850..a7ed55e5dd 100644 --- a/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/SeriesXmlSaver.cs @@ -60,11 +60,6 @@ namespace MediaBrowser.Providers.Savers builder.Append("" + SecurityElement.Escape(tvdb) + ""); } - if (!string.IsNullOrEmpty(item.Name)) - { - builder.Append("" + SecurityElement.Escape(item.Name) + ""); - } - if (series.Status.HasValue) { builder.Append("" + SecurityElement.Escape(series.Status.Value.ToString()) + ""); @@ -111,7 +106,6 @@ namespace MediaBrowser.Providers.Savers XmlSaverHelpers.Save(builder, xmlFilePath, new List { "id", - "SeriesName", "Status", "Network", "Airs_Time", @@ -120,6 +114,10 @@ namespace MediaBrowser.Providers.Savers // Don't preserve old series node "Series", + + "SeriesName", + + // Deprecated. No longer saving in this field. "AnimeSeriesIndex" }); } diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index 391ab7cfd9..6d681197e5 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -28,7 +28,10 @@ namespace MediaBrowser.Providers.Savers "AwardSummary", "BirthDate", "Budget", + + // Deprecated. No longer saving in this field. "certification", + "Chapters", "ContentRating", "CustomRating", @@ -40,22 +43,31 @@ namespace MediaBrowser.Providers.Savers "Genres", "Genre", "GamesDbId", + + // Deprecated. No longer saving in this field. "IMDB_ID", + "IMDB", + + // Deprecated. No longer saving in this field. "IMDbId", + "Language", "LocalTitle", "LockData", "LockedFields", "Format3D", "Metascore", + + // Deprecated. No longer saving in this field. "MPAARating", + "MusicBrainzArtistId", "MusicBrainzAlbumArtistId", "MusicBrainzAlbumId", "MusicBrainzReleaseGroupId", - // Old - not used anymore + // Deprecated. No longer saving in this field. "MusicbrainzId", "Overview", @@ -67,15 +79,24 @@ namespace MediaBrowser.Providers.Savers "Revenue", "RottenTomatoesId", "RunningTime", + + // Deprecated. No longer saving in this field. "Runtime", + "SortTitle", "Studios", "Tags", + + // Deprecated. No longer saving in this field. "TagLine", + "Taglines", "TMDbCollectionId", "TMDbId", + + // Deprecated. No longer saving in this field. "Trailer", + "Trailers", "TVcomId", "TvDbId", @@ -207,8 +228,6 @@ namespace MediaBrowser.Providers.Savers if (!string.IsNullOrEmpty(item.OfficialRating)) { builder.Append("" + SecurityElement.Escape(item.OfficialRating) + ""); - builder.Append("" + SecurityElement.Escape(item.OfficialRating) + ""); - builder.Append("" + SecurityElement.Escape(item.OfficialRating) + ""); } builder.Append("" + SecurityElement.Escape(item.DateCreated.ToLocalTime().ToString("G")) + ""); @@ -376,16 +395,13 @@ namespace MediaBrowser.Providers.Savers var timespan = TimeSpan.FromTicks(runTimeTicks.Value); builder.Append("" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + ""); - builder.Append("" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + ""); } var imdb = item.GetProviderId(MetadataProviders.Imdb); if (!string.IsNullOrEmpty(imdb)) { - builder.Append("" + SecurityElement.Escape(imdb) + ""); builder.Append("" + SecurityElement.Escape(imdb) + ""); - builder.Append("" + SecurityElement.Escape(imdb) + ""); } var tmdb = item.GetProviderId(MetadataProviders.Tmdb); diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs index 470bd7b3a9..5bc58af915 100644 --- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs @@ -77,7 +77,8 @@ namespace MediaBrowser.Providers.TV try { - AddImages(list, season.IndexNumber.Value, xmlPath, cancellationToken); + int seasonNumber = AdjustForSeriesOffset(series, season.IndexNumber.Value); + AddImages(list, seasonNumber, xmlPath, cancellationToken); } catch (FileNotFoundException) { @@ -115,6 +116,15 @@ namespace MediaBrowser.Providers.TV .ThenByDescending(i => i.VoteCount ?? 0); } + private int AdjustForSeriesOffset(Series series, int seasonNumber) + { + var offset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds); + if (offset != null) + return (int)(seasonNumber + offset); + + return seasonNumber; + } + private void AddImages(List list, int seasonNumber, string xmlPath, CancellationToken cancellationToken) { using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8)) diff --git a/MediaBrowser.Providers/TV/SeriesXmlParser.cs b/MediaBrowser.Providers/TV/SeriesXmlParser.cs index 0c220031c1..9f68ad7a4e 100644 --- a/MediaBrowser.Providers/TV/SeriesXmlParser.cs +++ b/MediaBrowser.Providers/TV/SeriesXmlParser.cs @@ -90,6 +90,8 @@ namespace MediaBrowser.Providers.TV break; } case "SeriesName": + // TODO: Deprecate in mid-2014 + // No longer saving this tag but will still read it for a while item.Name = reader.ReadElementContentAsString(); break; diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs index 36e349f600..ef37784501 100644 --- a/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbEpisodeImageProvider.cs @@ -62,8 +62,9 @@ namespace MediaBrowser.Providers.TV { // Process images var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId); + var indexOffset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds) ?? 0; - var files = TvdbEpisodeProvider.Current.GetEpisodeXmlFiles(episode.ParentIndexNumber, episode.IndexNumber, episode.IndexNumberEnd, seriesDataPath); + var files = TvdbEpisodeProvider.Current.GetEpisodeXmlFiles(episode.ParentIndexNumber + indexOffset, episode.IndexNumber, episode.IndexNumberEnd, seriesDataPath); var result = files.Select(i => GetImageInfo(i, cancellationToken)) .Where(i => i != null); diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs index 5f6bdfa042..922c29fe97 100644 --- a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs @@ -52,7 +52,7 @@ namespace MediaBrowser.Providers.TV try { - var item = FetchEpisodeData(searchInfo, seriesDataPath, cancellationToken); + var item = FetchEpisodeData(searchInfo, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken); if (item != null) { @@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.TV try { - result.Item = FetchEpisodeData(searchInfo, seriesDataPath, cancellationToken); + result.Item = FetchEpisodeData(searchInfo, seriesDataPath, searchInfo.SeriesProviderIds, cancellationToken); result.HasMetadata = result.Item != null; } catch (FileNotFoundException) @@ -213,7 +213,7 @@ namespace MediaBrowser.Providers.TV /// The series data path. /// The cancellation token. /// Task{System.Boolean}. - private Episode FetchEpisodeData(EpisodeInfo id, string seriesDataPath, CancellationToken cancellationToken) + private Episode FetchEpisodeData(EpisodeInfo id, string seriesDataPath, Dictionary seriesProviderIds, CancellationToken cancellationToken) { if (id.IndexNumber == null) { @@ -221,7 +221,8 @@ namespace MediaBrowser.Providers.TV } var episodeNumber = id.IndexNumber.Value; - var seasonNumber = id.ParentIndexNumber; + var seasonOffset = TvdbSeriesProvider.GetSeriesOffset(seriesProviderIds) ?? 0; + var seasonNumber = id.ParentIndexNumber + seasonOffset; if (seasonNumber == null) { diff --git a/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs index c0c103b7fb..5a9981c6e6 100644 --- a/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeasonImageProvider.cs @@ -22,8 +22,9 @@ namespace MediaBrowser.Providers.TV { public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder, IHasChangeMonitor { + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + private readonly IServerConfigurationManager _config; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IHttpClient _httpClient; private readonly IFileSystem _fileSystem; @@ -77,7 +78,8 @@ namespace MediaBrowser.Providers.TV try { - return GetImages(path, item.GetPreferredMetadataLanguage(), season.IndexNumber.Value, cancellationToken); + int seasonNumber = AdjustForSeriesOffset(series, season.IndexNumber.Value); + return GetImages(path, item.GetPreferredMetadataLanguage(), seasonNumber, cancellationToken); } catch (FileNotFoundException) { @@ -88,7 +90,16 @@ namespace MediaBrowser.Providers.TV return new RemoteImageInfo[] { }; } - private IEnumerable GetImages(string xmlPath, string preferredLanguage, int seasonNumber, CancellationToken cancellationToken) + private int AdjustForSeriesOffset(Series series, int seasonNumber) + { + var offset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds); + if (offset != null) + return (int) (seasonNumber + offset); + + return seasonNumber; + } + + internal static IEnumerable GetImages(string xmlPath, string preferredLanguage, int seasonNumber, CancellationToken cancellationToken) { var settings = new XmlReaderSettings { @@ -159,7 +170,7 @@ namespace MediaBrowser.Providers.TV .ToList(); } - private void AddImage(XmlReader reader, List images, int seasonNumber) + private static void AddImage(XmlReader reader, List images, int seasonNumber) { reader.MoveToContent(); @@ -186,7 +197,7 @@ namespace MediaBrowser.Providers.TV double rval; - if (double.TryParse(val, NumberStyles.Any, _usCulture, out rval)) + if (double.TryParse(val, NumberStyles.Any, UsCulture, out rval)) { rating = rval; } @@ -200,7 +211,7 @@ namespace MediaBrowser.Providers.TV int rval; - if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) + if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval)) { voteCount = rval; } @@ -237,12 +248,12 @@ namespace MediaBrowser.Providers.TV { int rval; - if (int.TryParse(resolutionParts[0], NumberStyles.Integer, _usCulture, out rval)) + if (int.TryParse(resolutionParts[0], NumberStyles.Integer, UsCulture, out rval)) { width = rval; } - if (int.TryParse(resolutionParts[1], NumberStyles.Integer, _usCulture, out rval)) + if (int.TryParse(resolutionParts[1], NumberStyles.Integer, UsCulture, out rval)) { height = rval; } @@ -285,7 +296,7 @@ namespace MediaBrowser.Providers.TV CommunityRating = rating, VoteCount = voteCount, Url = TVUtils.BannerUrl + url, - ProviderName = Name, + ProviderName = ProviderName, Language = language, Width = width, Height = height diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs index 761c774435..d1171f34bf 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs @@ -77,6 +77,10 @@ namespace MediaBrowser.Providers.TV try { + var seriesOffset = TvdbSeriesProvider.GetSeriesOffset(series.ProviderIds); + if (seriesOffset != null) + return TvdbSeasonImageProvider.GetImages(path, language, seriesOffset.Value + 1, cancellationToken); + return GetImages(path, language, cancellationToken); } catch (FileNotFoundException) diff --git a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs index 4b78c1a962..a76e101113 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs @@ -25,6 +25,8 @@ namespace MediaBrowser.Providers.TV { public class TvdbSeriesProvider : IRemoteMetadataProvider, IHasOrder { + internal const string TvdbSeriesOffset = "TvdbSeriesOffset"; + internal readonly SemaphoreSlim TvDbResourcePool = new SemaphoreSlim(2, 2); internal static TvdbSeriesProvider Current { get; private set; } private readonly IZipClient _zipClient; @@ -33,14 +35,16 @@ namespace MediaBrowser.Providers.TV private readonly IServerConfigurationManager _config; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly ILogger _logger; + private readonly ISeriesOrderManager _seriesOrder; - public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger) + public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ISeriesOrderManager seriesOrder) { _zipClient = zipClient; _httpClient = httpClient; _fileSystem = fileSystem; _config = config; _logger = logger; + _seriesOrder = seriesOrder; Current = this; } @@ -92,11 +96,35 @@ namespace MediaBrowser.Providers.TV result.HasMetadata = true; FetchSeriesData(result.Item, seriesId, cancellationToken); + await FindAnimeSeriesIndex(result.Item, itemId).ConfigureAwait(false); } return result; } + private async Task FindAnimeSeriesIndex(Series series, SeriesInfo info) + { + var index = await _seriesOrder.FindSeriesIndex(SeriesOrderTypes.Anime, series.Name); + if (index == null) + return; + + var offset = info.AnimeSeriesIndex - index; + series.SetProviderId(TvdbSeriesOffset, offset.ToString()); + } + + internal static int? GetSeriesOffset(Dictionary seriesProviderIds) + { + string offsetString; + if (!seriesProviderIds.TryGetValue(TvdbSeriesOffset, out offsetString)) + return null; + + int offset; + if (int.TryParse(offsetString, out offset)) + return offset; + + return null; + } + /// /// Fetches the series data. /// diff --git a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs index 415205cb11..cb3621fd17 100644 --- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -26,6 +26,7 @@ namespace MediaBrowser.Server.Implementations.Configuration { UpdateItemsByNamePath(); UpdateTranscodingTempPath(); + UpdateMetadataPath(); } /// @@ -76,6 +77,16 @@ namespace MediaBrowser.Server.Implementations.Configuration Configuration.ItemsByNamePath; } + /// + /// Updates the metadata path. + /// + private void UpdateMetadataPath() + { + ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = string.IsNullOrEmpty(Configuration.MetadataPath) ? + null : + Configuration.MetadataPath; + } + /// /// Updates the transcoding temporary path. /// @@ -98,6 +109,7 @@ namespace MediaBrowser.Server.Implementations.Configuration ValidateItemByNamePath(newConfig); ValidateTranscodingTempPath(newConfig); ValidatePathSubstitutions(newConfig); + ValidateMetadataPath(newConfig); base.ReplaceConfiguration(newConfiguration); } @@ -166,5 +178,25 @@ namespace MediaBrowser.Server.Implementations.Configuration } } } + + /// + /// Validates the metadata path. + /// + /// The new configuration. + /// + private void ValidateMetadataPath(ServerConfiguration newConfig) + { + var newPath = newConfig.MetadataPath; + + if (!string.IsNullOrWhiteSpace(newPath) + && !string.Equals(Configuration.MetadataPath ?? string.Empty, newPath)) + { + // Validate + if (!Directory.Exists(newPath)) + { + throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath)); + } + } + } } } diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 12d3eadfd3..c6c1ec050d 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; @@ -52,12 +53,14 @@ namespace MediaBrowser.Server.Implementations.Drawing private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationPaths _appPaths; + private readonly IMediaEncoder _mediaEncoder; - public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer) + public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder) { _logger = logger; _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; + _mediaEncoder = mediaEncoder; _appPaths = appPaths; _saveImageSizeTimer = new Timer(SaveImageSizeCallback, null, Timeout.Infinite, Timeout.Infinite); @@ -66,7 +69,7 @@ namespace MediaBrowser.Server.Implementations.Drawing try { - sizeDictionary = jsonSerializer.DeserializeFromFile>(ImageSizeFile) ?? + sizeDictionary = jsonSerializer.DeserializeFromFile>(ImageSizeFile) ?? new Dictionary(); } catch (FileNotFoundException) @@ -213,6 +216,39 @@ namespace MediaBrowser.Server.Implementations.Drawing try { + var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed.HasValue; + + //if (!hasPostProcessing) + //{ + // using (var outputStream = await _mediaEncoder.EncodeImage(new ImageEncodingOptions + // { + // InputPath = originalImagePath, + // MaxHeight = options.MaxHeight, + // MaxWidth = options.MaxWidth, + // Height = options.Height, + // Width = options.Width, + // Quality = options.Quality, + // Format = options.OutputFormat == ImageOutputFormat.Original ? Path.GetExtension(originalImagePath).TrimStart('.') : options.OutputFormat.ToString().ToLower() + + // }, CancellationToken.None).ConfigureAwait(false)) + // { + // using (var outputMemoryStream = new MemoryStream()) + // { + // // Save to the memory stream + // await outputStream.CopyToAsync(outputMemoryStream).ConfigureAwait(false); + + // var bytes = outputMemoryStream.ToArray(); + + // await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + + // // kick off a task to cache the result + // await CacheResizedImage(cacheFilePath, bytes).ConfigureAwait(false); + // } + + // return; + // } + //} + using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { // Copy to memory stream to avoid Image locking file @@ -241,8 +277,8 @@ namespace MediaBrowser.Server.Implementations.Drawing thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality; thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic; thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality; - thumbnailGraph.CompositingMode = string.IsNullOrEmpty(options.BackgroundColor) && !options.UnplayedCount.HasValue && !options.AddPlayedIndicator && !options.PercentPlayed.HasValue ? - CompositingMode.SourceCopy : + thumbnailGraph.CompositingMode = !hasPostProcessing ? + CompositingMode.SourceCopy : CompositingMode.SourceOver; SetBackgroundColor(thumbnailGraph, options); @@ -263,7 +299,7 @@ namespace MediaBrowser.Server.Implementations.Drawing await toStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); // kick off a task to cache the result - CacheResizedImage(cacheFilePath, bytes, semaphore); + await CacheResizedImage(cacheFilePath, bytes).ConfigureAwait(false); } } } @@ -272,11 +308,9 @@ namespace MediaBrowser.Server.Implementations.Drawing } } } - catch + finally { semaphore.Release(); - - throw; } } @@ -285,33 +319,26 @@ namespace MediaBrowser.Server.Implementations.Drawing /// /// The cache file path. /// The bytes. - /// The semaphore. - private void CacheResizedImage(string cacheFilePath, byte[] bytes, SemaphoreSlim semaphore) + /// Task. + private async Task CacheResizedImage(string cacheFilePath, byte[] bytes) { - Task.Run(async () => + try { - try - { - var parentPath = Path.GetDirectoryName(cacheFilePath); + var parentPath = Path.GetDirectoryName(cacheFilePath); - Directory.CreateDirectory(parentPath); + Directory.CreateDirectory(parentPath); - // Save to the cache location - using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) - { - // Save to the filestream - await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - } - } - catch (Exception ex) + // Save to the cache location + using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { - _logger.ErrorException("Error writing to image cache file {0}", ex, cacheFilePath); - } - finally - { - semaphore.Release(); + // Save to the filestream + await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); } - }); + } + catch (Exception ex) + { + _logger.ErrorException("Error writing to image cache file {0}", ex, cacheFilePath); + } } /// @@ -519,7 +546,7 @@ namespace MediaBrowser.Server.Implementations.Drawing { filename += "iv=" + IndicatorVersion; } - + if (!string.IsNullOrEmpty(backgroundColor)) { filename += "b=" + backgroundColor; diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 0a0b3f4bcd..9279fd8d73 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -249,17 +249,24 @@ namespace MediaBrowser.Server.Implementations.IO // Creating a FileSystemWatcher over the LAN can take hundreds of milliseconds, so wrap it in a Task to do them all in parallel Task.Run(() => { - var newWatcher = new FileSystemWatcher(path, "*") { IncludeSubdirectories = true, InternalBufferSize = 32767 }; + try + { + var newWatcher = new FileSystemWatcher(path, "*") + { + IncludeSubdirectories = true, + InternalBufferSize = 32767 + }; - newWatcher.Created += watcher_Changed; - newWatcher.Deleted += watcher_Changed; - newWatcher.Renamed += watcher_Changed; - newWatcher.Changed += watcher_Changed; + newWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName | + NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size; - newWatcher.Error += watcher_Error; + newWatcher.Created += watcher_Changed; + newWatcher.Deleted += watcher_Changed; + newWatcher.Renamed += watcher_Changed; + newWatcher.Changed += watcher_Changed; + + newWatcher.Error += watcher_Error; - try - { if (_fileSystemWatchers.TryAdd(path, newWatcher)) { newWatcher.EnableRaisingEvents = true; @@ -272,11 +279,7 @@ namespace MediaBrowser.Server.Implementations.IO } } - catch (IOException ex) - { - Logger.ErrorException("Error watching path: {0}", ex, path); - } - catch (PlatformNotSupportedException ex) + catch (Exception ex) { Logger.ErrorException("Error watching path: {0}", ex, path); } @@ -346,7 +349,9 @@ namespace MediaBrowser.Server.Implementations.IO { try { - OnWatcherChanged(e); + Logger.Debug("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); + + ReportFileSystemChanged(e.FullPath); } catch (Exception ex) { @@ -354,13 +359,6 @@ namespace MediaBrowser.Server.Implementations.IO } } - private void OnWatcherChanged(FileSystemEventArgs e) - { - Logger.Debug("Watcher sees change of type " + e.ChangeType + " to " + e.FullPath); - - ReportFileSystemChanged(e.FullPath); - } - public void ReportFileSystemChanged(string path) { if (string.IsNullOrEmpty(path)) @@ -370,12 +368,9 @@ namespace MediaBrowser.Server.Implementations.IO var filename = Path.GetFileName(path); - // Ignore certain files - if (!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)) - { - return; - } + var monitorPath = !(!string.IsNullOrEmpty(filename) && _alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)); + // Ignore certain files var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); // If the parent of an ignored path has a change event, ignore that too @@ -416,12 +411,15 @@ namespace MediaBrowser.Server.Implementations.IO })) { - return; + monitorPath = false; } - // Avoid implicitly captured closure - var affectedPath = path; - _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath); + if (monitorPath) + { + // Avoid implicitly captured closure + var affectedPath = path; + _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath); + } lock (_timerLock) { diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 06028d37ec..2ee843f09b 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -260,6 +260,8 @@ namespace MediaBrowser.Server.Implementations.Library public event EventHandler> UserCreated; + private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); + /// /// Creates the user. /// @@ -279,19 +281,28 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name)); } - var user = InstantiateNewUser(name); + await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - var list = Users.ToList(); - list.Add(user); - Users = list; + try + { + var user = InstantiateNewUser(name); - user.DateLastSaved = DateTime.UtcNow; + var list = Users.ToList(); + list.Add(user); + Users = list; - await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false); + user.DateLastSaved = DateTime.UtcNow; - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false); - return user; + EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + + return user; + } + finally + { + _userListLock.Release(); + } } /// @@ -325,23 +336,32 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name)); } - await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false); - - var path = user.ConfigurationFilePath; + await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); try { - File.Delete(path); + await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false); + + var path = user.ConfigurationFilePath; + + try + { + File.Delete(path); + } + catch (IOException ex) + { + _logger.ErrorException("Error deleting file {0}", ex, path); + } + + // Force this to be lazy loaded again + Users = await LoadUsers().ConfigureAwait(false); + + OnUserDeleted(user); } - catch (IOException ex) + finally { - _logger.ErrorException("Error deleting file {0}", ex, path); + _userListLock.Release(); } - - // Force this to be lazy loaded again - Users = await LoadUsers().ConfigureAwait(false); - - OnUserDeleted(user); } /// diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 6a4c3930a2..db8786d62b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1498,8 +1498,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var programs = _programs.ToList(); - var startDate = programs.Select(i => i.Value.StartDate).Min(); - var endDate = programs.Select(i => i.Value.StartDate).Max(); + var startDate = _programs.Count == 0 ? DateTime.MinValue : + programs.Select(i => i.Value.StartDate).Min(); + + var endDate = programs.Count == 0 ? DateTime.MinValue : + programs.Select(i => i.Value.StartDate).Max(); return new GuideInfo { diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 73a12caf2c..ea7ef2ed65 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -48,14 +48,6 @@ ..\packages\Alchemy.2.2.1\lib\net40\Alchemy.dll - - False - ..\packages\MediaBrowser.BdInfo.1.0.0.10\lib\net35\BDInfo.dll - - - False - ..\packages\MediaBrowser.BdInfo.1.0.0.10\lib\net35\DvdLib.dll - False ..\packages\Mono.Nat.1.2.3\lib\Net40\Mono.Nat.dll @@ -105,7 +97,6 @@ Properties\SharedVersion.cs - @@ -191,7 +182,6 @@ - diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs index f74865d2c7..7237ffee5c 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -178,7 +178,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder { Directory.CreateDirectory(Path.GetDirectoryName(path)); - using (var stream = await _encoder.ExtractImage(inputPath, type, false, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false)) + using (var stream = await _encoder.ExtractVideoImage(inputPath, type, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false)) { using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteMediaStreamsRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteMediaStreamsRepository.cs index f4e7fd0a6b..fde1e7f216 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteMediaStreamsRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteMediaStreamsRepository.cs @@ -37,6 +37,8 @@ namespace MediaBrowser.Server.Implementations.Persistence var createTableCommand = "create table if not exists mediastreams "; + // Add PixelFormat column + createTableCommand += "(ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PRIMARY KEY (ItemId, StreamIndex))"; string[] queries = { diff --git a/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs b/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs index 0e2f9e1b57..d806db1e0b 100644 --- a/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs +++ b/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs @@ -146,5 +146,16 @@ namespace MediaBrowser.Server.Implementations.Roku RequestContentType = "application/json" }); } + + + public Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken) + { + return SendCommand(new WebSocketMessage + { + MessageType = "Command", + Data = command + + }, cancellationToken); + } } } diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs index c36c49df0e..df2a5f83c2 100644 --- a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs +++ b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs @@ -1,6 +1,6 @@ -using System; -using MediaBrowser.Common.Implementations; +using MediaBrowser.Common.Implementations; using MediaBrowser.Controller; +using System; using System.IO; namespace MediaBrowser.Server.Implementations @@ -239,14 +239,20 @@ namespace MediaBrowser.Server.Implementations } } + private string _internalMetadataPath; public string InternalMetadataPath { get { - return Path.Combine(DataPath, "metadata"); + return _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata")); + } + set + { + _internalMetadataPath = value; } } + public string GetInternalMetadataPath(Guid id) { var idString = id.ToString("N"); diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs index 70d7ac071f..ddf4ec2ca7 100644 --- a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs +++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs @@ -198,5 +198,17 @@ namespace MediaBrowser.Server.Implementations.Session }, cancellationToken); } + + public Task SendGenericCommand(GenericCommand command, CancellationToken cancellationToken) + { + var socket = GetActiveSocket(); + + return socket.SendAsync(new WebSocketMessage + { + MessageType = "Command", + Data = command + + }, cancellationToken); + } } } diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index f04536190b..d82e880c98 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -1,7 +1,6 @@  - diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index e49244edf0..b7e9017d6e 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -33,13 +33,14 @@ using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Themes; using MediaBrowser.Dlna; using MediaBrowser.Dlna.PlayTo; +using MediaBrowser.MediaEncoding.BdInfo; +using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; using MediaBrowser.Model.Updates; using MediaBrowser.Providers.Manager; using MediaBrowser.Server.Implementations; -using MediaBrowser.Server.Implementations.BdInfo; using MediaBrowser.Server.Implementations.Channels; using MediaBrowser.Server.Implementations.Collections; using MediaBrowser.Server.Implementations.Configuration; @@ -157,6 +158,7 @@ namespace MediaBrowser.ServerApplication private IHttpServer HttpServer { get; set; } private IDtoService DtoService { get; set; } private IImageProcessor ImageProcessor { get; set; } + private ISeriesOrderManager SeriesOrderManager { get; set; } /// /// Gets or sets the media encoder. @@ -441,7 +443,7 @@ namespace MediaBrowser.ServerApplication FileOrganizationRepository = await GetFileOrganizationRepository().ConfigureAwait(false); RegisterSingleInstance(FileOrganizationRepository); - UserManager = new UserManager(Logger, ServerConfigurationManager, UserRepository); + UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository); RegisterSingleInstance(UserManager); LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager); @@ -453,6 +455,9 @@ namespace MediaBrowser.ServerApplication ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager); RegisterSingleInstance(ProviderManager); + SeriesOrderManager = new SeriesOrderManager(); + RegisterSingleInstance(SeriesOrderManager); + RegisterSingleInstance(() => new SearchEngine(LogManager, LibraryManager, UserManager)); SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager); @@ -462,13 +467,19 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(HttpServer, false); progress.Report(10); - ServerManager = new ServerManager(this, JsonSerializer, Logger, ServerConfigurationManager); + ServerManager = new ServerManager(this, JsonSerializer, LogManager.GetLogger("ServerManager"), ServerConfigurationManager); RegisterSingleInstance(ServerManager); LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager); RegisterSingleInstance(LocalizationManager); - ImageProcessor = new ImageProcessor(Logger, ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer); + var innerProgress = new ActionableProgress(); + innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15)); + + await RegisterMediaEncoder(innerProgress).ConfigureAwait(false); + progress.Report(90); + + ImageProcessor = new ImageProcessor(LogManager.GetLogger("ImageProcessor"), ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer, MediaEncoder); RegisterSingleInstance(ImageProcessor); DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager); @@ -477,17 +488,11 @@ namespace MediaBrowser.ServerApplication var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer); RegisterSingleInstance(newsService); - var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, Logger, LibraryMonitor, LibraryManager, ServerConfigurationManager, FileSystemManager, ProviderManager); + var fileOrganizationService = new FileOrganizationService(TaskManager, FileOrganizationRepository, LogManager.GetLogger("FileOrganizationService"), LibraryMonitor, LibraryManager, ServerConfigurationManager, FileSystemManager, ProviderManager); RegisterSingleInstance(fileOrganizationService); progress.Report(15); - var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15)); - - await RegisterMediaEncoder(innerProgress).ConfigureAwait(false); - progress.Report(90); - EncodingManager = new EncodingManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, MediaEncoder); RegisterSingleInstance(EncodingManager); @@ -498,7 +503,7 @@ namespace MediaBrowser.ServerApplication var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger); RegisterSingleInstance(appThemeManager); - var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, JsonSerializer); + var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LogManager.GetLogger("DLNA")); RegisterSingleInstance(dlnaManager); var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); @@ -680,6 +685,8 @@ namespace MediaBrowser.ServerApplication GetExports(), GetExports()); + SeriesOrderManager.AddParts(GetExports()); + ImageProcessor.AddParts(GetExports()); LiveTvManager.AddParts(GetExports()); @@ -795,7 +802,7 @@ namespace MediaBrowser.ServerApplication list.Add(typeof(ApiEntryPoint).Assembly); // Include composable parts in the Dashboard assembly - list.Add(typeof(DashboardInfo).Assembly); + list.Add(typeof(DashboardService).Assembly); // Include composable parts in the Model assembly list.Add(typeof(SystemInfo).Assembly); @@ -815,7 +822,10 @@ namespace MediaBrowser.ServerApplication // Server implementations list.Add(typeof(ServerApplicationPaths).Assembly); - // Dlna implementations + // MediaEncoding + list.Add(typeof(MediaEncoder).Assembly); + + // Dlna list.Add(typeof(PlayToServerEntryPoint).Assembly); list.AddRange(Assemblies.GetAssembliesWithParts()); diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 6afe9367e5..3d490a1f8c 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -1,12 +1,11 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Constants; using MediaBrowser.Common.Implementations.Logging; -using MediaBrowser.Common.Implementations.Updates; -using MediaBrowser.Controller; using MediaBrowser.Model.Logging; using MediaBrowser.Server.Implementations; using MediaBrowser.ServerApplication.Native; using MediaBrowser.ServerApplication.Splash; +using MediaBrowser.ServerApplication.Updates; using Microsoft.Win32; using System; using System.Configuration.Install; @@ -490,7 +489,7 @@ namespace MediaBrowser.ServerApplication try { var serviceName = _isRunningAsService ? BackgroundService.Name : string.Empty; - new ApplicationUpdater().UpdateApplication(MBApplication.MBServer, appPaths, updateArchive, logger, serviceName); + new ApplicationUpdater().UpdateApplication(appPaths, updateArchive, logger, serviceName); // And just let the app exit so it can update return true; diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 227d0dd1d3..b49e100ab2 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -145,6 +145,7 @@ SplashForm.cs + @@ -195,6 +196,10 @@ {734098eb-6dc1-4dd0-a1ca-3140dcd2737c} MediaBrowser.Dlna + + {0bd82fa6-eb8a-4452-8af5-74f9c3849451} + MediaBrowser.MediaEncoding + {7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b} MediaBrowser.Model diff --git a/MediaBrowser.Common.Implementations/Updates/ApplicationUpdater.cs b/MediaBrowser.ServerApplication/Updates/ApplicationUpdater.cs similarity index 86% rename from MediaBrowser.Common.Implementations/Updates/ApplicationUpdater.cs rename to MediaBrowser.ServerApplication/Updates/ApplicationUpdater.cs index e24ff30645..9f3e44cb0f 100644 --- a/MediaBrowser.Common.Implementations/Updates/ApplicationUpdater.cs +++ b/MediaBrowser.ServerApplication/Updates/ApplicationUpdater.cs @@ -3,14 +3,8 @@ using MediaBrowser.Model.Logging; using System.Diagnostics; using System.IO; -namespace MediaBrowser.Common.Implementations.Updates +namespace MediaBrowser.ServerApplication.Updates { - public enum MBApplication - { - MBServer, - MBTheater - } - /// /// Update the specified application using the specified archive /// @@ -18,7 +12,7 @@ namespace MediaBrowser.Common.Implementations.Updates { private const string UpdaterExe = "Mediabrowser.Updater.exe"; private const string UpdaterDll = "Mediabrowser.InstallUtil.dll"; - public void UpdateApplication(MBApplication app, IApplicationPaths appPaths, string archive, ILogger logger, string restartServiceName) + public void UpdateApplication(IApplicationPaths appPaths, string archive, ILogger logger, string restartServiceName) { // First see if there is a version file and read that in var version = "Unknown"; @@ -39,7 +33,7 @@ namespace MediaBrowser.Common.Implementations.Updates logger.Info("Copying updater dependencies to temporary location"); File.Copy(source, tempUpdaterDll, true); - var product = app == MBApplication.MBTheater ? "mbt" : "server"; + const string product = "server"; // Our updater needs SS and ionic source = Path.Combine(appPaths.ProgramSystemPath, "ServiceStack.Text.dll"); File.Copy(source, Path.Combine(Path.GetTempPath(), "ServiceStack.Text.dll"), true); diff --git a/MediaBrowser.WebDashboard/Api/DashboardInfo.cs b/MediaBrowser.WebDashboard/Api/DashboardInfo.cs deleted file mode 100644 index 78cc5a758c..0000000000 --- a/MediaBrowser.WebDashboard/Api/DashboardInfo.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediaBrowser.Model.Session; -using MediaBrowser.Model.System; -using MediaBrowser.Model.Tasks; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.WebDashboard.Api -{ - /// - /// Class DashboardInfo - /// - public class DashboardInfo - { - /// - /// Gets or sets the system info. - /// - /// The system info. - public SystemInfo SystemInfo { get; set; } - - /// - /// Gets or sets the running tasks. - /// - /// The running tasks. - public List RunningTasks { get; set; } - - /// - /// Gets or sets the application update task id. - /// - /// The application update task id. - public Guid ApplicationUpdateTaskId { get; set; } - - /// - /// Gets or sets the active connections. - /// - /// The active connections. - public List ActiveConnections { get; set; } - } - -} diff --git a/MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs b/MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs deleted file mode 100644 index af0f9e3a05..0000000000 --- a/MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs +++ /dev/null @@ -1,62 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Logging; -using System.Threading.Tasks; - -namespace MediaBrowser.WebDashboard.Api -{ - /// - /// Class DashboardInfoWebSocketListener - /// - class DashboardInfoWebSocketListener : BasePeriodicWebSocketListener - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "DashboardInfo"; } - } - - private readonly IServerApplicationHost _appHost; - - /// - /// Gets or sets the task manager. - /// - /// The task manager. - private readonly ITaskManager _taskManager; - - private readonly ISessionManager _sessionManager; - private readonly IDtoService _dtoService; - - /// - /// Initializes a new instance of the class. - /// - /// The app host. - /// The logger. - /// The task manager. - /// The session manager. - public DashboardInfoWebSocketListener(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, ISessionManager sessionManager, IDtoService dtoService) - : base(logger) - { - _appHost = appHost; - _taskManager = taskManager; - _sessionManager = sessionManager; - _dtoService = dtoService; - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{IEnumerable{TaskInfo}}. - protected override Task GetDataToSend(object state) - { - return Task.FromResult(DashboardService.GetDashboardInfo(_appHost, _taskManager, _sessionManager, _dtoService)); - } - } -} diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 88f86632b7..99afbbdd70 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -1,16 +1,13 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; -using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Tasks; using ServiceStack; +using ServiceStack.Web; using System; using System.Collections.Generic; using System.IO; @@ -18,7 +15,6 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; -using ServiceStack.Web; namespace MediaBrowser.WebDashboard.Api { @@ -66,14 +62,6 @@ namespace MediaBrowser.WebDashboard.Api public string V { get; set; } } - /// - /// Class GetDashboardInfo - /// - [Route("/dashboard/dashboardInfo", "GET")] - public class GetDashboardInfo : IReturn - { - } - /// /// Class DashboardService /// @@ -97,12 +85,6 @@ namespace MediaBrowser.WebDashboard.Api /// The request context. public IRequest Request { get; set; } - /// - /// Gets or sets the task manager. - /// - /// The task manager. - private readonly ITaskManager _taskManager; - /// /// The _app host /// @@ -113,24 +95,18 @@ namespace MediaBrowser.WebDashboard.Api /// private readonly IServerConfigurationManager _serverConfigurationManager; - private readonly ISessionManager _sessionManager; - private readonly IDtoService _dtoService; private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. /// - /// The task manager. /// The app host. /// The server configuration manager. - /// The session manager. - public DashboardService(ITaskManager taskManager, IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, ISessionManager sessionManager, IDtoService dtoService, IFileSystem fileSystem) + /// The file system. + public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem) { - _taskManager = taskManager; _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; - _sessionManager = sessionManager; - _dtoService = dtoService; _fileSystem = fileSystem; } @@ -163,45 +139,6 @@ namespace MediaBrowser.WebDashboard.Api return Path.Combine(DashboardUIPath, virtualPath.Replace('/', Path.DirectorySeparatorChar)); } - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetDashboardInfo request) - { - var result = GetDashboardInfo(_appHost, _taskManager, _sessionManager, _dtoService); - - return ResultFactory.GetOptimizedResult(Request, result); - } - - /// - /// Gets the dashboard info. - /// - /// The app host. - /// The task manager. - /// The connection manager. - /// DashboardInfo. - public static DashboardInfo GetDashboardInfo(IServerApplicationHost appHost, - ITaskManager taskManager, - ISessionManager connectionManager, IDtoService dtoService) - { - var connections = connectionManager.Sessions.Where(i => i.IsActive).ToList(); - - return new DashboardInfo - { - SystemInfo = appHost.GetSystemInfo(), - - RunningTasks = taskManager.ScheduledTasks.Where(i => i.State == TaskState.Running || i.State == TaskState.Cancelling) - .Select(ScheduledTaskHelpers.GetTaskInfo) - .ToList(), - - ApplicationUpdateTaskId = taskManager.ScheduledTasks.First(t => t.ScheduledTask.GetType().Name.Equals("SystemUpdateTask", StringComparison.OrdinalIgnoreCase)).Id, - - ActiveConnections = connections.Select(dtoService.GetSessionInfoDto).ToList() - }; - } - /// /// Gets the specified request. /// @@ -473,6 +410,7 @@ namespace MediaBrowser.WebDashboard.Api "alphapicker.js", "addpluginpage.js", "advancedconfigurationpage.js", + "advancedpaths.js", "advancedserversettings.js", "metadataadvanced.js", "appsplayback.js", @@ -484,6 +422,8 @@ namespace MediaBrowser.WebDashboard.Api "dashboardinfo.js", "dashboardpage.js", "directorybrowser.js", + "dlnaprofile.js", + "dlnaprofiles.js", "dlnasettings.js", "editcollectionitems.js", "edititemmetadata.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index a0dbae3d4c..6a8cc49b1a 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -63,9 +63,7 @@ Properties\SharedVersion.cs - - @@ -85,6 +83,9 @@ + + PreserveNewest + PreserveNewest @@ -202,8 +203,12 @@ PreserveNewest - - + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -219,6 +224,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -483,6 +494,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -504,6 +518,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 7ac1580659..7dc06fb0ce 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -41,6 +41,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.ServerApplicat EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Dlna", "MediaBrowser.Dlna\MediaBrowser.Dlna.csproj", "{734098EB-6DC1-4DD0-A1CA-3140DCD2737C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{0BD82FA6-EB8A-4452-8AF5-74F9C3849451}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -247,6 +249,20 @@ Global {734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|Win32.ActiveCfg = Release|Any CPU {734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|x64.ActiveCfg = Release|Any CPU {734098EB-6DC1-4DD0-A1CA-3140DCD2737C}.Release|x86.ActiveCfg = Release|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|Win32.ActiveCfg = Debug|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|x64.ActiveCfg = Debug|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Debug|x86.ActiveCfg = Debug|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Any CPU.Build.0 = Release|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|Win32.ActiveCfg = Release|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|x64.ActiveCfg = Release|Any CPU + {0BD82FA6-EB8A-4452-8AF5-74F9C3849451}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index c550b350c6..a3b4533dd9 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.345 + 3.0.346 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index a6fa5c152c..b80f673e38 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.345 + 3.0.346 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index cc87b00300..eccfcccd2a 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.345 + 3.0.346 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - +