diff options
59 files changed, 1290 insertions, 650 deletions
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index ba6809f4d..663e1be28 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -1,17 +1,17 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using ServiceStack; +using ServiceStack.Text.Controller; +using ServiceStack.Web; using System; using System.Collections.Generic; using System.Drawing; @@ -19,8 +19,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using ServiceStack.Text.Controller; -using ServiceStack.Web; namespace MediaBrowser.Api.Images { @@ -39,18 +37,6 @@ namespace MediaBrowser.Api.Images public string Id { get; set; } } - [Route("/LiveTv/Channels/{Id}/Images", "GET")] - [Api(Description = "Gets information about an item's images")] - public class GetChannelImageInfos : IReturn<List<ImageInfo>> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - [Route("/Artists/{Name}/Images", "GET")] [Route("/Genres/{Name}/Images", "GET")] [Route("/GameGenres/{Name}/Images", "GET")] @@ -80,20 +66,7 @@ namespace MediaBrowser.Api.Images [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } - - [Route("/LiveTv/Channels/{Id}/Images/{Type}", "GET")] - [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "GET")] - [Api(Description = "Gets an item image")] - public class GetChannelImage : ImageRequest - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - + /// <summary> /// Class UpdateItemImageIndex /// </summary> @@ -270,19 +243,6 @@ namespace MediaBrowser.Api.Images public Guid Id { get; set; } } - [Route("/LiveTv/Channels/{Id}/Images/{Type}", "DELETE")] - [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "DELETE")] - [Api(Description = "Deletes an item image")] - public class DeleteChannelImage : DeleteImageRequest, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - /// <summary> /// Class PostUserImage /// </summary> @@ -358,38 +318,13 @@ namespace MediaBrowser.Api.Images public Stream RequestStream { get; set; } } - [Route("/LiveTv/Channels/{Id}/Images/{Type}", "POST")] - [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "POST")] - [Api(Description = "Posts an item image")] - public class PostChannelImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// <summary> - /// The raw Http Request Input Stream - /// </summary> - /// <value>The request stream.</value> - public Stream RequestStream { get; set; } - } - /// <summary> /// Class ImageService /// </summary> public class ImageService : BaseApiService { - /// <summary> - /// The _user manager - /// </summary> private readonly IUserManager _userManager; - /// <summary> - /// The _library manager - /// </summary> private readonly ILibraryManager _libraryManager; private readonly IApplicationPaths _appPaths; @@ -400,12 +335,11 @@ namespace MediaBrowser.Api.Images private readonly IDtoService _dtoService; private readonly IImageProcessor _imageProcessor; - private readonly ILiveTvManager _liveTv; /// <summary> /// Initializes a new instance of the <see cref="ImageService" /> class. /// </summary> - public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, ILiveTvManager liveTv) + public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor) { _userManager = userManager; _libraryManager = libraryManager; @@ -414,7 +348,6 @@ namespace MediaBrowser.Api.Images _itemRepo = itemRepo; _dtoService = dtoService; _imageProcessor = imageProcessor; - _liveTv = liveTv; } /// <summary> @@ -431,15 +364,6 @@ namespace MediaBrowser.Api.Images return ToOptimizedResult(result); } - public object Get(GetChannelImageInfos request) - { - var item = _liveTv.GetChannel(request.Id); - - var result = GetItemImageInfos(item); - - return ToOptimizedResult(result); - } - public object Get(GetItemByNameImageInfos request) { var result = GetItemByNameImageInfos(request); @@ -540,7 +464,7 @@ namespace MediaBrowser.Api.Images return list; } - private ImageInfo GetImageInfo(string path, BaseItem item, int? imageIndex, ImageType type) + private ImageInfo GetImageInfo(string path, IHasImages item, int? imageIndex, ImageType type) { try { @@ -567,13 +491,6 @@ namespace MediaBrowser.Api.Images } } - public object Get(GetChannelImage request) - { - var item = _liveTv.GetChannel(request.Id); - - return GetImage(request, item); - } - /// <summary> /// Gets the specified request. /// </summary> @@ -659,20 +576,6 @@ namespace MediaBrowser.Api.Images Task.WaitAll(task); } - public void Post(PostChannelImage request) - { - var pathInfo = PathInfo.Parse(Request.PathInfo); - var id = pathInfo.GetArgumentValue<string>(2); - - request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue<string>(4), true); - - var item = _liveTv.GetChannel(id); - - var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType); - - Task.WaitAll(task); - } - /// <summary> /// Deletes the specified request. /// </summary> @@ -699,15 +602,6 @@ namespace MediaBrowser.Api.Images Task.WaitAll(task); } - public void Delete(DeleteChannelImage request) - { - var item = _liveTv.GetChannel(request.Id); - - var task = item.DeleteImage(request.Type, request.Index); - - Task.WaitAll(task); - } - /// <summary> /// Deletes the specified request. /// </summary> @@ -762,71 +656,9 @@ namespace MediaBrowser.Api.Images /// <param name="newIndex">The new index.</param> /// <returns>Task.</returns> /// <exception cref="System.ArgumentException">The change index operation is only applicable to backdrops and screenshots</exception> - private Task UpdateItemIndex(BaseItem item, ImageType type, int currentIndex, int newIndex) + private Task UpdateItemIndex(IHasImages item, ImageType type, int currentIndex, int newIndex) { - string file1; - string file2; - - if (type == ImageType.Screenshot) - { - var hasScreenshots = (IHasScreenshots)item; - file1 = hasScreenshots.ScreenshotImagePaths[currentIndex]; - file2 = hasScreenshots.ScreenshotImagePaths[newIndex]; - } - else if (type == ImageType.Backdrop) - { - file1 = item.BackdropImagePaths[currentIndex]; - file2 = item.BackdropImagePaths[newIndex]; - } - else - { - throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots"); - } - - SwapFiles(file1, file2); - - // Directory watchers should repeat this, but do a quick refresh first - return item.RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false); - } - - /// <summary> - /// Swaps the files. - /// </summary> - /// <param name="file1">The file1.</param> - /// <param name="file2">The file2.</param> - private void SwapFiles(string file1, string file2) - { - Directory.CreateDirectory(_appPaths.TempDirectory); - - var temp1 = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp"); - var temp2 = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp"); - - // Copying over will fail against hidden files - RemoveHiddenAttribute(file1); - RemoveHiddenAttribute(file2); - - File.Copy(file1, temp1); - File.Copy(file2, temp2); - - File.Copy(temp1, file2, true); - File.Copy(temp2, file1, true); - - File.Delete(temp1); - File.Delete(temp2); - } - - private void RemoveHiddenAttribute(string path) - { - var currentFile = new FileInfo(path); - - // This will fail if the file is hidden - if (currentFile.Exists) - { - if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) - { - currentFile.Attributes &= ~FileAttributes.Hidden; - } - } + return item.SwapImages(type, currentIndex, newIndex); } /// <summary> @@ -837,7 +669,7 @@ namespace MediaBrowser.Api.Images /// <returns>System.Object.</returns> /// <exception cref="ResourceNotFoundException"> /// </exception> - private object GetImage(ImageRequest request, BaseItem item) + public object GetImage(ImageRequest request, IHasImages item) { var imagePath = GetImagePath(request, item); @@ -926,7 +758,7 @@ namespace MediaBrowser.Api.Images /// <param name="request">The request.</param> /// <param name="item">The item.</param> /// <returns>System.String.</returns> - private string GetImagePath(ImageRequest request, BaseItem item) + private string GetImagePath(ImageRequest request, IHasImages item) { var index = request.Index ?? 0; @@ -941,7 +773,7 @@ namespace MediaBrowser.Api.Images /// <param name="imageType">Type of the image.</param> /// <param name="mimeType">Type of the MIME.</param> /// <returns>Task.</returns> - private async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType) + public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType) { using (var reader = new StreamReader(inputStream)) { diff --git a/MediaBrowser.Api/Images/ImageWriter.cs b/MediaBrowser.Api/Images/ImageWriter.cs index 5d1ee140d..2ace05125 100644 --- a/MediaBrowser.Api/Images/ImageWriter.cs +++ b/MediaBrowser.Api/Images/ImageWriter.cs @@ -2,12 +2,11 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using ServiceStack; +using ServiceStack.Web; using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using ServiceStack.Web; namespace MediaBrowser.Api.Images { @@ -27,7 +26,7 @@ namespace MediaBrowser.Api.Images /// Gets or sets the item. /// </summary> /// <value>The item.</value> - public BaseItem Item { get; set; } + public IHasImages Item { get; set; } /// <summary> /// The original image date modified /// </summary> diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 17a87a861..e15e6ef6e 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -146,7 +146,7 @@ namespace MediaBrowser.Api private async Task UpdateItem(UpdateChannel request) { - var item = _liveTv.GetChannel(request.Id); + var item = _liveTv.GetInternalChannel(request.Id); UpdateItem(request, item); diff --git a/MediaBrowser.Api/LiveTv/LiveTvImageService.cs b/MediaBrowser.Api/LiveTv/LiveTvImageService.cs new file mode 100644 index 000000000..84926e164 --- /dev/null +++ b/MediaBrowser.Api/LiveTv/LiveTvImageService.cs @@ -0,0 +1,184 @@ +using MediaBrowser.Api.Images; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using ServiceStack; +using ServiceStack.Text.Controller; +using ServiceStack.Web; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.LiveTv +{ + [Route("/LiveTv/Channels/{Id}/Images/{Type}", "POST")] + [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "POST")] + [Api(Description = "Posts an item image")] + public class PostChannelImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// <summary> + /// The raw Http Request Input Stream + /// </summary> + /// <value>The request stream.</value> + public Stream RequestStream { get; set; } + } + + [Route("/LiveTv/Channels/{Id}/Images/{Type}", "DELETE")] + [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "DELETE")] + [Api(Description = "Deletes an item image")] + public class DeleteChannelImage : DeleteImageRequest, IReturnVoid + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + } + [Route("/LiveTv/Channels/{Id}/Images/{Type}", "GET")] + [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "GET")] + [Api(Description = "Gets an item image")] + public class GetChannelImage : ImageRequest + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/LiveTv/Recordings/{Id}/Images/{Type}", "GET")] + [Route("/LiveTv/Recordings/{Id}/Images/{Type}/{Index}", "GET")] + [Api(Description = "Gets an item image")] + public class GetRecordingImage : ImageRequest + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/LiveTv/Programs/{Id}/Images/{Type}", "GET")] + [Route("/LiveTv/Programs/{Id}/Images/{Type}/{Index}", "GET")] + [Api(Description = "Gets an item image")] + public class GetProgramImage : ImageRequest + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Program Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/LiveTv/Channels/{Id}/Images", "GET")] + [Api(Description = "Gets information about an item's images")] + public class GetChannelImageInfos : IReturn<List<ImageInfo>> + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + public class LiveTvImageService : BaseApiService + { + private readonly ILiveTvManager _liveTv; + + private readonly IUserManager _userManager; + + private readonly ILibraryManager _libraryManager; + + private readonly IApplicationPaths _appPaths; + + private readonly IProviderManager _providerManager; + + private readonly IItemRepository _itemRepo; + private readonly IDtoService _dtoService; + private readonly IImageProcessor _imageProcessor; + + public LiveTvImageService(ILiveTvManager liveTv, IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor) + { + _liveTv = liveTv; + _userManager = userManager; + _libraryManager = libraryManager; + _appPaths = appPaths; + _providerManager = providerManager; + _itemRepo = itemRepo; + _dtoService = dtoService; + _imageProcessor = imageProcessor; + } + + public object Get(GetChannelImageInfos request) + { + var item = _liveTv.GetInternalChannel(request.Id); + + var result = GetImageService().GetItemImageInfos(item); + + return ToOptimizedResult(result); + } + + public object Get(GetChannelImage request) + { + var item = _liveTv.GetInternalChannel(request.Id); + + return GetImageService().GetImage(request, item); + } + + public object Get(GetRecordingImage request) + { + var item = _liveTv.GetInternalRecording(request.Id, CancellationToken.None).Result; + + return GetImageService().GetImage(request, item); + } + + public void Post(PostChannelImage request) + { + var pathInfo = PathInfo.Parse(Request.PathInfo); + var id = pathInfo.GetArgumentValue<string>(2); + + request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue<string>(4), true); + + var item = _liveTv.GetInternalChannel(id); + + var task = GetImageService().PostImage(item, request.RequestStream, request.Type, Request.ContentType); + + Task.WaitAll(task); + } + + public void Delete(DeleteChannelImage request) + { + var item = _liveTv.GetInternalChannel(request.Id); + + var task = item.DeleteImage(request.Type, request.Index); + + Task.WaitAll(task); + } + + private ImageService GetImageService() + { + return new ImageService(_userManager, _libraryManager, _appPaths, _providerManager, _itemRepo, _dtoService, + _imageProcessor); + } + } +} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index bae3716bc..0732ee00c 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -90,6 +90,7 @@ <Compile Include="Library\LibraryHelpers.cs" /> <Compile Include="Library\LibraryService.cs" /> <Compile Include="Library\LibraryStructureService.cs" /> + <Compile Include="LiveTv\LiveTvImageService.cs" /> <Compile Include="LiveTv\LiveTvService.cs" /> <Compile Include="LocalizationService.cs" /> <Compile Include="MoviesService.cs" /> diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 966d48174..2bd02cfcf 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1,11 +1,9 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; -using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Persistence; @@ -110,7 +108,7 @@ namespace MediaBrowser.Api.Playback /// <returns>System.String.</returns> protected virtual string GetOutputFileExtension(StreamState state) { - return Path.GetExtension(state.Url); + return Path.GetExtension(state.RequestedUrl); } /// <summary> @@ -187,7 +185,7 @@ namespace MediaBrowser.Api.Playback { var args = string.Empty; - if (state.Item.LocationType == LocationType.Remote) + if (state.IsRemote) { return string.Empty; } @@ -308,7 +306,7 @@ namespace MediaBrowser.Api.Playback return args.Trim(); } - + /// <summary> /// If we're going to put a fixed size on the command line, this will calculate it /// </summary> @@ -331,7 +329,7 @@ namespace MediaBrowser.Api.Playback string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)) { - assSubtitleParam = GetTextSubtitleParam((Video)state.Item, state.SubtitleStream, request.StartTimeTicks, performTextSubtitleConversion); + assSubtitleParam = GetTextSubtitleParam(state, request.StartTimeTicks, performTextSubtitleConversion); } } @@ -402,14 +400,14 @@ namespace MediaBrowser.Api.Playback /// <summary> /// Gets the text subtitle param. /// </summary> - /// <param name="video">The video.</param> - /// <param name="subtitleStream">The subtitle stream.</param> + /// <param name="state">The state.</param> /// <param name="startTimeTicks">The start time ticks.</param> /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param> /// <returns>System.String.</returns> - protected string GetTextSubtitleParam(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion) + protected string GetTextSubtitleParam(StreamState state, long? startTimeTicks, bool performConversion) { - var path = subtitleStream.IsExternal ? GetConvertedAssPath(video, subtitleStream, startTimeTicks, performConversion) : GetExtractedAssPath(video, subtitleStream, startTimeTicks, performConversion); + var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, startTimeTicks, performConversion) : + GetExtractedAssPath(state, startTimeTicks, performConversion); if (string.IsNullOrEmpty(path)) { @@ -422,22 +420,21 @@ namespace MediaBrowser.Api.Playback /// <summary> /// Gets the extracted ass path. /// </summary> - /// <param name="video">The video.</param> - /// <param name="subtitleStream">The subtitle stream.</param> + /// <param name="state">The state.</param> /// <param name="startTimeTicks">The start time ticks.</param> /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param> /// <returns>System.String.</returns> - private string GetExtractedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion) + private string GetExtractedAssPath(StreamState state, long? startTimeTicks, bool performConversion) { var offset = TimeSpan.FromTicks(startTimeTicks ?? 0); - var path = FFMpegManager.Instance.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass"); + var path = FFMpegManager.Instance.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream, offset, ".ass"); if (performConversion) { InputType type; - var inputPath = MediaEncoderHelpers.GetInputArgument(video, null, out type); + var inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.IsRemote, state.VideoType, state.IsoType, null, state.PlayableStreamFileNames, out type); try { @@ -445,7 +442,7 @@ namespace MediaBrowser.Api.Playback Directory.CreateDirectory(parentPath); - var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, subtitleStream.Index, offset, path, CancellationToken.None); + var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, offset, path, CancellationToken.None); Task.WaitAll(task); } @@ -461,22 +458,16 @@ namespace MediaBrowser.Api.Playback /// <summary> /// Gets the converted ass path. /// </summary> - /// <param name="video">The video.</param> + /// <param name="mediaPath">The media path.</param> /// <param name="subtitleStream">The subtitle stream.</param> /// <param name="startTimeTicks">The start time ticks.</param> /// <param name="performConversion">if set to <c>true</c> [perform conversion].</param> /// <returns>System.String.</returns> - private string GetConvertedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion) + private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, long? startTimeTicks, bool performConversion) { - // If it's already ass, no conversion neccessary - //if (string.Equals(Path.GetExtension(subtitleStream.Path), ".ass", StringComparison.OrdinalIgnoreCase)) - //{ - // return subtitleStream.Path; - //} - var offset = TimeSpan.FromTicks(startTimeTicks ?? 0); - var path = FFMpegManager.Instance.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass"); + var path = FFMpegManager.Instance.GetSubtitleCachePath(mediaPath, subtitleStream, offset, ".ass"); if (performConversion) { @@ -524,25 +515,15 @@ namespace MediaBrowser.Api.Playback /// <summary> /// Gets the probe size argument. /// </summary> - /// <param name="item">The item.</param> + /// <param name="mediaPath">The media path.</param> + /// <param name="isVideo">if set to <c>true</c> [is video].</param> + /// <param name="videoType">Type of the video.</param> + /// <param name="isoType">Type of the iso.</param> /// <returns>System.String.</returns> - protected string GetProbeSizeArgument(BaseItem item) + protected string GetProbeSizeArgument(string mediaPath, bool isVideo, VideoType? videoType, IsoType? isoType) { - var type = InputType.AudioFile; - - if (item is Audio) - { - type = MediaEncoderHelpers.GetInputType(item.Path, null, null); - } - else - { - var video = item as Video; - - if (video != null) - { - type = MediaEncoderHelpers.GetInputType(item.Path, video.VideoType, video.IsoType); - } - } + var type = !isVideo ? MediaEncoderHelpers.GetInputType(mediaPath, null, null) : + MediaEncoderHelpers.GetInputType(mediaPath, videoType, isoType); return MediaEncoder.GetProbeSizeArgument(type); } @@ -652,22 +633,19 @@ namespace MediaBrowser.Api.Playback /// <summary> /// Gets the input argument. /// </summary> - /// <param name="item">The item.</param> - /// <param name="isoMount">The iso mount.</param> + /// <param name="state">The state.</param> /// <returns>System.String.</returns> - protected string GetInputArgument(BaseItem item, IIsoMount isoMount) + protected string GetInputArgument(StreamState state) { var type = InputType.AudioFile; - var inputPath = new[] { item.Path }; - - var video = item as Video; + var inputPath = new[] { state.MediaPath }; - if (video != null) + if (state.IsInputVideo) { - if (!(video.VideoType == VideoType.Iso && isoMount == null)) + if (!(state.VideoType == VideoType.Iso && state.IsoMount == null)) { - inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type); + inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.IsRemote, state.VideoType, state.IsoType, state.IsoMount, state.PlayableStreamFileNames, out type); } } @@ -686,11 +664,9 @@ namespace MediaBrowser.Api.Playback Directory.CreateDirectory(parentPath); - var video = state.Item as Video; - - if (video != null && video.VideoType == VideoType.Iso && video.IsoType.HasValue && IsoManager.CanMount(video.Path)) + if (state.IsInputVideo && state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath)) { - state.IsoMount = await IsoManager.Mount(video.Path, CancellationToken.None).ConfigureAwait(false); + state.IsoMount = await IsoManager.Mount(state.MediaPath, CancellationToken.None).ConfigureAwait(false); } var process = new Process @@ -715,7 +691,7 @@ namespace MediaBrowser.Api.Playback EnableRaisingEvents = true }; - ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, video != null, state.Request.StartTimeTicks, state.Item.Path, state.Request.DeviceId); + ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.IsInputVideo, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId); Logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments); @@ -754,13 +730,13 @@ namespace MediaBrowser.Api.Playback } // Allow a small amount of time to buffer a little - if (state.Item is Video) + if (state.IsInputVideo) { await Task.Delay(500).ConfigureAwait(false); } // This is arbitrary, but add a little buffer time when internet streaming - if (state.Item.LocationType == LocationType.Remote) + if (state.IsRemote) { await Task.Delay(4000).ConfigureAwait(false); } @@ -787,11 +763,11 @@ namespace MediaBrowser.Api.Playback /// <summary> /// Gets the user agent param. /// </summary> - /// <param name="item">The item.</param> + /// <param name="path">The path.</param> /// <returns>System.String.</returns> - protected string GetUserAgentParam(BaseItem item) + protected string GetUserAgentParam(string path) { - var useragent = GetUserAgent(item); + var useragent = GetUserAgent(path); if (!string.IsNullOrEmpty(useragent)) { @@ -804,11 +780,11 @@ namespace MediaBrowser.Api.Playback /// <summary> /// Gets the user agent. /// </summary> - /// <param name="item">The item.</param> + /// <param name="path">The path.</param> /// <returns>System.String.</returns> - protected string GetUserAgent(BaseItem item) + protected string GetUserAgent(string path) { - if (item.Path.IndexOf("apple.com", StringComparison.OrdinalIgnoreCase) != -1) + if (path.IndexOf("apple.com", StringComparison.OrdinalIgnoreCase) != -1) { return "QuickTime/7.7.4"; } @@ -852,8 +828,6 @@ namespace MediaBrowser.Api.Playback { var item = DtoService.GetItemByDtoId(request.Id); - var media = (IHasMediaStreams)item; - var url = Request.PathInfo; if (!request.AudioCodec.HasValue) @@ -863,11 +837,25 @@ namespace MediaBrowser.Api.Playback var state = new StreamState { - Item = item, Request = request, - Url = url + RequestedUrl = url, + MediaPath = item.Path, + IsRemote = item.LocationType == LocationType.Remote }; + var video = item as Video; + + if (video != null) + { + state.IsInputVideo = true; + state.VideoType = video.VideoType; + state.IsoType = video.IsoType; + + state.PlayableStreamFileNames = video.PlayableStreamFileNames == null + ? new List<string>() + : video.PlayableStreamFileNames.ToList(); + } + var videoRequest = request as VideoStreamRequest; var mediaStreams = ItemRepository.GetMediaStreams(new MediaStreamQuery diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index abe82ab77..d30788452 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.MediaInfo; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; @@ -247,7 +246,7 @@ namespace MediaBrowser.Api.Playback.Hls /// <returns>System.String.</returns> protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions) { - var probeSize = GetProbeSizeArgument(state.Item); + var probeSize = GetProbeSizeArgument(state.MediaPath, state.IsInputVideo, state.VideoType, state.IsoType); var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; @@ -262,9 +261,9 @@ namespace MediaBrowser.Api.Playback.Hls var args = string.Format("{0}{1} {2} {3} -i {4}{5} -threads {6} {7} {8} -sc_threshold 0 {9} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{10}\"", itsOffset, probeSize, - GetUserAgentParam(state.Item), + GetUserAgentParam(state.MediaPath), GetFastSeekCommandLineParameter(state.Request), - GetInputArgument(state.Item, state.IsoMount), + GetInputArgument(state), GetSlowSeekCommandLineParameter(state.Request), threads, GetMapArgs(state), @@ -275,7 +274,7 @@ namespace MediaBrowser.Api.Playback.Hls if (hlsVideoRequest != null) { - if (hlsVideoRequest.AppendBaselineStream && state.Item is Video) + if (hlsVideoRequest.AppendBaselineStream && state.IsInputVideo) { var lowBitratePath = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath) + "-low.m3u8"); diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index dcd0bc036..5889dc169 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -105,7 +105,7 @@ namespace MediaBrowser.Api.Playback.Progressive return string.Format("{0} -i {1}{2} -threads {3}{4} {5} -id3v2_version 3 -write_id3v1 1 \"{6}\"", GetFastSeekCommandLineParameter(request), - GetInputArgument(state.Item, state.IsoMount), + GetInputArgument(state), GetSlowSeekCommandLineParameter(request), threads, vn, diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 46836a04c..fcbcc5623 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -1,16 +1,12 @@ -using MediaBrowser.Api.Images; -using MediaBrowser.Common.IO; +using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using System.Collections.Generic; using System.IO; @@ -51,9 +47,7 @@ namespace MediaBrowser.Api.Playback.Progressive // Try to infer based on the desired video codec if (videoRequest != null && videoRequest.VideoCodec.HasValue) { - var video = state.Item as Video; - - if (video != null) + if (state.IsInputVideo) { switch (videoRequest.VideoCodec.Value) { @@ -72,9 +66,7 @@ namespace MediaBrowser.Api.Playback.Progressive // Try to infer based on the desired audio codec if (state.Request.AudioCodec.HasValue) { - var audio = state.Item as Audio; - - if (audio != null) + if (!state.IsInputVideo) { switch (state.Request.AudioCodec.Value) { @@ -188,16 +180,11 @@ namespace MediaBrowser.Api.Playback.Progressive { var state = GetState(request); - if (request.AlbumArt) - { - return GetAlbumArtResponse(state); - } - var responseHeaders = new Dictionary<string, string>(); - if (request.Static && state.Item.LocationType == LocationType.Remote) + if (request.Static && state.IsRemote) { - return GetStaticRemoteStreamResult(state.Item, responseHeaders, isHeadRequest).Result; + return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result; } var outputPath = GetOutputFilePath(state); @@ -210,7 +197,7 @@ namespace MediaBrowser.Api.Playback.Progressive if (request.Static) { - return ResultFactory.GetStaticFileResult(Request, state.Item.Path, FileShare.Read, responseHeaders, isHeadRequest); + return ResultFactory.GetStaticFileResult(Request, state.MediaPath, FileShare.Read, responseHeaders, isHeadRequest); } if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)) @@ -224,19 +211,19 @@ namespace MediaBrowser.Api.Playback.Progressive /// <summary> /// Gets the static remote stream result. /// </summary> - /// <param name="item">The item.</param> + /// <param name="mediaPath">The media path.</param> /// <param name="responseHeaders">The response headers.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> /// <returns>Task{System.Object}.</returns> - private async Task<object> GetStaticRemoteStreamResult(BaseItem item, Dictionary<string, string> responseHeaders, bool isHeadRequest) + private async Task<object> GetStaticRemoteStreamResult(string mediaPath, Dictionary<string, string> responseHeaders, bool isHeadRequest) { responseHeaders["Accept-Ranges"] = "none"; var httpClient = new HttpClient(); - using (var message = new HttpRequestMessage(HttpMethod.Get, item.Path)) + using (var message = new HttpRequestMessage(HttpMethod.Get, mediaPath)) { - var useragent = GetUserAgent(item); + var useragent = GetUserAgent(mediaPath); if (!string.IsNullOrEmpty(useragent)) { @@ -273,47 +260,6 @@ namespace MediaBrowser.Api.Playback.Progressive } /// <summary> - /// Gets the album art response. - /// </summary> - /// <param name="state">The state.</param> - /// <returns>System.Object.</returns> - private object GetAlbumArtResponse(StreamState state) - { - var request = new GetItemImage - { - MaxWidth = 800, - MaxHeight = 800, - Type = ImageType.Primary, - Id = state.Item.Id.ToString() - }; - - // Try and find some image to return - if (!state.Item.HasImage(ImageType.Primary)) - { - if (state.Item.HasImage(ImageType.Backdrop)) - { - request.Type = ImageType.Backdrop; - } - else if (state.Item.HasImage(ImageType.Thumb)) - { - request.Type = ImageType.Thumb; - } - else if (state.Item.HasImage(ImageType.Logo)) - { - request.Type = ImageType.Logo; - } - } - - return new ImageService(UserManager, LibraryManager, ServerConfigurationManager.ApplicationPaths, null, ItemRepository, DtoService, ImageProcessor, null) - { - Logger = Logger, - Request = Request, - ResultFactory = ResultFactory - - }.Get(request); - } - - /// <summary> /// Gets the stream result. /// </summary> /// <param name="state">The state.</param> diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 27af9e182..2d09b5b6f 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -3,7 +3,6 @@ using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.IO; @@ -89,9 +88,7 @@ namespace MediaBrowser.Api.Playback.Progressive /// <returns>System.String.</returns> protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions) { - var video = (Video)state.Item; - - var probeSize = GetProbeSizeArgument(state.Item); + var probeSize = GetProbeSizeArgument(state.MediaPath, state.IsInputVideo, state.VideoType, state.IsoType); // Get the output codec name var videoCodec = GetVideoCodec(state.VideoRequest); @@ -108,9 +105,9 @@ namespace MediaBrowser.Api.Playback.Progressive return string.Format("{0} {1} {2} -i {3}{4}{5} {6} {7} -threads {8} {9}{10} \"{11}\"", probeSize, - GetUserAgentParam(state.Item), + GetUserAgentParam(state.MediaPath), GetFastSeekCommandLineParameter(state.Request), - GetInputArgument(video, state.IsoMount), + GetInputArgument(state), GetSlowSeekCommandLineParameter(state.Request), keyFrame, GetMapArgs(state), diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 3c2ea5a13..3b2cfbd2b 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -1,14 +1,13 @@ -using System.IO; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; +using System.Collections.Generic; +using System.IO; namespace MediaBrowser.Api.Playback { public class StreamState { - public string Url { get; set; } + public string RequestedUrl { get; set; } public StreamRequest Request { get; set; } @@ -29,12 +28,22 @@ namespace MediaBrowser.Api.Playback public MediaStream SubtitleStream { get; set; } - public BaseItem Item { get; set; } - /// <summary> /// Gets or sets the iso mount. /// </summary> /// <value>The iso mount.</value> public IIsoMount IsoMount { get; set; } + + public string MediaPath { get; set; } + + public bool IsRemote { get; set; } + + public bool IsInputVideo { get; set; } + + public VideoType VideoType { get; set; } + + public IsoType? IsoType { get; set; } + + public List<string> PlayableStreamFileNames { get; set; } } } diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index 25e22ab59..a1eb2c3a1 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -153,7 +153,7 @@ namespace MediaBrowser.Api if (item.HasImage(ImageType.Primary)) { - result.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, item.GetImage(ImageType.Primary)); + result.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, item.GetImagePath(ImageType.Primary)); } var episode = item as Episode; diff --git a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs index ed9baf3b2..616981d50 100644 --- a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs +++ b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs @@ -216,6 +216,48 @@ namespace MediaBrowser.Common.Implementations.IO return new FileStream(path, mode, access, share); } + + /// <summary> + /// Swaps the files. + /// </summary> + /// <param name="file1">The file1.</param> + /// <param name="file2">The file2.</param> + public void SwapFiles(string file1, string file2) + { + var temp1 = Path.GetTempFileName(); + var temp2 = Path.GetTempFileName(); + + // Copying over will fail against hidden files + RemoveHiddenAttribute(file1); + RemoveHiddenAttribute(file2); + + File.Copy(file1, temp1, true); + File.Copy(file2, temp2, true); + + File.Copy(temp1, file2, true); + File.Copy(temp2, file1, true); + + File.Delete(temp1); + File.Delete(temp2); + } + + /// <summary> + /// Removes the hidden attribute. + /// </summary> + /// <param name="path">The path.</param> + private void RemoveHiddenAttribute(string path) + { + var currentFile = new FileInfo(path); + + // This will fail if the file is hidden + if (currentFile.Exists) + { + if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) + { + currentFile.Attributes &= ~FileAttributes.Hidden; + } + } + } } /// <summary> diff --git a/MediaBrowser.Common/IO/IFileSystem.cs b/MediaBrowser.Common/IO/IFileSystem.cs index d307b74e5..8fba63195 100644 --- a/MediaBrowser.Common/IO/IFileSystem.cs +++ b/MediaBrowser.Common/IO/IFileSystem.cs @@ -74,5 +74,12 @@ namespace MediaBrowser.Common.IO /// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param> /// <returns>FileStream.</returns> FileStream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share, bool isAsync = false); + + /// <summary> + /// Swaps the files. + /// </summary> + /// <param name="file1">The file1.</param> + /// <param name="file2">The file2.</param> + void SwapFiles(string file1, string file2); } } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 1a8583489..2ecf3ec9a 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.Controller.Drawing /// <param name="item">The item.</param> /// <param name="imageType">Type of the image.</param> /// <returns>IEnumerable{IImageEnhancer}.</returns> - IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType); + IEnumerable<IImageEnhancer> GetSupportedEnhancers(IHasImages item, ImageType imageType); /// <summary> /// Gets the image cache tag. @@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Drawing /// <param name="imageType">Type of the image.</param> /// <param name="imagePath">The image path.</param> /// <returns>Guid.</returns> - Guid GetImageCacheTag(BaseItem item, ImageType imageType, string imagePath); + Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath); /// <summary> /// Gets the image cache tag. @@ -67,7 +67,7 @@ namespace MediaBrowser.Controller.Drawing /// <param name="dateModified">The date modified.</param> /// <param name="imageEnhancers">The image enhancers.</param> /// <returns>Guid.</returns> - Guid GetImageCacheTag(BaseItem item, ImageType imageType, string originalImagePath, DateTime dateModified, + Guid GetImageCacheTag(IHasImages item, ImageType imageType, string originalImagePath, DateTime dateModified, List<IImageEnhancer> imageEnhancers); /// <summary> @@ -85,6 +85,6 @@ namespace MediaBrowser.Controller.Drawing /// <param name="imageType">Type of the image.</param> /// <param name="imageIndex">Index of the image.</param> /// <returns>Task{System.String}.</returns> - Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex); + Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex); } } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index ce4bf6c32..506d6fd3d 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Drawing { public class ImageProcessingOptions { - public BaseItem Item { get; set; } + public IHasImages Item { get; set; } public ImageType ImageType { get; set; } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 541887598..2aa3e5ecc 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class BaseItem /// </summary> - public abstract class BaseItem : IHasProviderIds, ILibraryItem + public abstract class BaseItem : IHasProviderIds, ILibraryItem, IHasImages, IHasUserData { protected BaseItem() { @@ -132,8 +132,8 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public string PrimaryImagePath { - get { return GetImage(ImageType.Primary); } - set { SetImage(ImageType.Primary, value); } + get { return this.GetImagePath(ImageType.Primary); } + set { this.SetImagePath(ImageType.Primary, value); } } /// <summary> @@ -1310,31 +1310,10 @@ namespace MediaBrowser.Controller.Entities /// Gets an image /// </summary> /// <param name="type">The type.</param> - /// <returns>System.String.</returns> - /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception> - public string GetImage(ImageType type) - { - if (type == ImageType.Backdrop) - { - throw new ArgumentException("Backdrops should be accessed using Item.Backdrops"); - } - if (type == ImageType.Screenshot) - { - throw new ArgumentException("Screenshots should be accessed using Item.Screenshots"); - } - - string val; - Images.TryGetValue(type, out val); - return val; - } - - /// <summary> - /// Gets an image - /// </summary> - /// <param name="type">The type.</param> + /// <param name="imageIndex">Index of the image.</param> /// <returns><c>true</c> if the specified type has image; otherwise, <c>false</c>.</returns> /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception> - public bool HasImage(ImageType type) + public bool HasImage(ImageType type, int imageIndex) { if (type == ImageType.Backdrop) { @@ -1345,16 +1324,10 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentException("Screenshots should be accessed using Item.Screenshots"); } - return !string.IsNullOrEmpty(GetImage(type)); + return !string.IsNullOrEmpty(this.GetImagePath(type)); } - /// <summary> - /// Sets an image - /// </summary> - /// <param name="type">The type.</param> - /// <param name="path">The path.</param> - /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception> - public void SetImage(ImageType type, string path) + public void SetImagePath(ImageType type, int index, string path) { if (type == ImageType.Backdrop) { @@ -1423,10 +1396,10 @@ namespace MediaBrowser.Controller.Entities else { // Delete the source file - DeleteImagePath(GetImage(type)); + DeleteImagePath(this.GetImagePath(type)); // Remove it from the item - SetImage(type, null); + this.SetImagePath(type, null); } // Refresh metadata @@ -1597,13 +1570,13 @@ namespace MediaBrowser.Controller.Entities { if (imageType == ImageType.Backdrop) { - return BackdropImagePaths[imageIndex]; + return BackdropImagePaths.Count > imageIndex ? BackdropImagePaths[imageIndex] : null; } if (imageType == ImageType.Screenshot) { var hasScreenshots = (IHasScreenshots)this; - return hasScreenshots.ScreenshotImagePaths[imageIndex]; + return hasScreenshots.ScreenshotImagePaths.Count > imageIndex ? hasScreenshots.ScreenshotImagePaths[imageIndex] : null; } if (imageType == ImageType.Chapter) @@ -1611,7 +1584,9 @@ namespace MediaBrowser.Controller.Entities return ItemRepository.GetChapter(Id, imageIndex).ImagePath; } - return GetImage(imageType); + string val; + Images.TryGetValue(imageType, out val); + return val; } /// <summary> @@ -1658,5 +1633,21 @@ namespace MediaBrowser.Controller.Entities { return new[] { Path }; } + + public Task SwapImages(ImageType type, int index1, int index2) + { + if (type != ImageType.Screenshot && type != ImageType.Backdrop) + { + throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots"); + } + + var file1 = GetImagePath(type, index1); + var file2 = GetImagePath(type, index2); + + FileSystem.SwapFiles(file1, file2); + + // Directory watchers should repeat this, but do a quick refresh first + return RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false); + } } } diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs new file mode 100644 index 000000000..f8cbfc6ab --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -0,0 +1,97 @@ +using MediaBrowser.Model.Entities; +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Entities +{ + public interface IHasImages + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + string Name { get; } + + /// <summary> + /// Gets the path. + /// </summary> + /// <value>The path.</value> + string Path { get; } + + /// <summary> + /// Gets the identifier. + /// </summary> + /// <value>The identifier.</value> + Guid Id { get; } + + /// <summary> + /// Gets the image path. + /// </summary> + /// <param name="imageType">Type of the image.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <returns>System.String.</returns> + string GetImagePath(ImageType imageType, int imageIndex); + + /// <summary> + /// Gets the image date modified. + /// </summary> + /// <param name="imagePath">The image path.</param> + /// <returns>DateTime.</returns> + DateTime GetImageDateModified(string imagePath); + + /// <summary> + /// Sets the image. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="index">The index.</param> + /// <param name="path">The path.</param> + void SetImagePath(ImageType type, int index, string path); + + /// <summary> + /// Determines whether the specified type has image. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="imageIndex">Index of the image.</param> + /// <returns><c>true</c> if the specified type has image; otherwise, <c>false</c>.</returns> + bool HasImage(ImageType type, int imageIndex); + + /// <summary> + /// Swaps the images. + /// </summary> + /// <param name="type">The type.</param> + /// <param name="index1">The index1.</param> + /// <param name="index2">The index2.</param> + /// <returns>Task.</returns> + Task SwapImages(ImageType type, int index1, int index2); + } + + public static class HasImagesExtensions + { + /// <summary> + /// Gets the image path. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="imageType">Type of the image.</param> + /// <returns>System.String.</returns> + public static string GetImagePath(this IHasImages item, ImageType imageType) + { + return item.GetImagePath(imageType, 0); + } + + public static bool HasImage(this IHasImages item, ImageType imageType) + { + return item.HasImage(imageType, 0); + } + + /// <summary> + /// Sets the image path. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="imageType">Type of the image.</param> + /// <param name="path">The path.</param> + public static void SetImagePath(this IHasImages item, ImageType imageType, string path) + { + item.SetImagePath(imageType, 0, path); + } + } +} diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs new file mode 100644 index 000000000..780181a61 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasUserData.cs @@ -0,0 +1,15 @@ + +namespace MediaBrowser.Controller.Entities +{ + /// <summary> + /// Interface IHasUserData + /// </summary> + public interface IHasUserData + { + /// <summary> + /// Gets the user data key. + /// </summary> + /// <returns>System.String.</returns> + string GetUserDataKey(); + } +} diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 1565de4f8..71785fa7a 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -183,7 +183,9 @@ namespace MediaBrowser.Controller.Entities.TV episodes = episodes.Where(i => !i.IsVirtualUnaired); } - return LibraryManager.Sort(episodes, user, new[] { ItemSortBy.AiredEpisodeOrder }, SortOrder.Ascending) + var sortBy = seasonNumber == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder; + + return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending) .Cast<Episode>(); } diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index d6d5f99aa..2bec9e3de 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Library /// <param name="reason">The reason.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); + Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); /// <summary> /// Gets the user data. diff --git a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs index 87e7f647a..ba328ff75 100644 --- a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs +++ b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs @@ -37,6 +37,6 @@ namespace MediaBrowser.Controller.Library /// Gets or sets the item. /// </summary> /// <value>The item.</value> - public BaseItem Item { get; set; } + public IHasUserData Item { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/Channel.cs b/MediaBrowser.Controller/LiveTv/Channel.cs deleted file mode 100644 index 7186cfaf3..000000000 --- a/MediaBrowser.Controller/LiveTv/Channel.cs +++ /dev/null @@ -1,75 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.LiveTv; -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace MediaBrowser.Controller.LiveTv -{ - public class Channel : BaseItem, IItemByName - { - public Channel() - { - UserItemCountList = new List<ItemByNameCounts>(); - } - - /// <summary> - /// Gets the user data key. - /// </summary> - /// <returns>System.String.</returns> - public override string GetUserDataKey() - { - return "Channel-" + Name; - } - - [IgnoreDataMember] - public List<ItemByNameCounts> UserItemCountList { get; set; } - - /// <summary> - /// Gets or sets the number. - /// </summary> - /// <value>The number.</value> - public string ChannelNumber { get; set; } - - /// <summary> - /// Get or sets the Id. - /// </summary> - /// <value>The id of the channel.</value> - public string ChannelId { get; set; } - - /// <summary> - /// Gets or sets the name of the service. - /// </summary> - /// <value>The name of the service.</value> - public string ServiceName { get; set; } - - /// <summary> - /// Gets or sets the type of the channel. - /// </summary> - /// <value>The type of the channel.</value> - public ChannelType ChannelType { get; set; } - - public bool? HasProviderImage { get; set; } - - protected override string CreateSortName() - { - double number = 0; - - if (!string.IsNullOrEmpty(ChannelNumber)) - { - double.TryParse(ChannelNumber, out number); - } - - return number.ToString("000-") + (Name ?? string.Empty); - } - - public override string MediaType - { - get - { - return ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; - } - } - } -} diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index bb0636673..9bc37fcc0 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -32,9 +32,15 @@ namespace MediaBrowser.Controller.LiveTv public ChannelType ChannelType { get; set; } /// <summary> - /// Set this value to true or false if it is known via channel info whether there is an image or not. - /// Leave it null if the only way to determine is by requesting the image and handling the failure. + /// Supply the image path if it can be accessed directly from the file system /// </summary> - public bool? HasImage { get; set; } + /// <value>The image path.</value> + public string ImagePath { get; set; } + + /// <summary> + /// Supply the image url if it can be downloaded + /// </summary> + /// <value>The image URL.</value> + public string ImageUrl { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 8e711d28c..e7226e9b5 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -144,9 +144,17 @@ namespace MediaBrowser.Controller.LiveTv /// </summary> /// <param name="id">The identifier.</param> /// <returns>Channel.</returns> - Channel GetChannel(string id); + LiveTvChannel GetInternalChannel(string id); /// <summary> + /// Gets the recording. + /// </summary> + /// <param name="id">The identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>LiveTvRecording.</returns> + Task<LiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken); + + /// <summary> /// Gets the program. /// </summary> /// <param name="id">The identifier.</param> diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index 2cd768a93..1c88629f0 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.Controller.LiveTv Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken); /// <summary> - /// Gets the channel image asynchronous. + /// Gets the channel image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ChannelInfo /// </summary> /// <param name="channelId">The channel identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> @@ -87,7 +87,7 @@ namespace MediaBrowser.Controller.LiveTv Task<ImageResponseInfo> GetChannelImageAsync(string channelId, CancellationToken cancellationToken); /// <summary> - /// Gets the recording image asynchronous. + /// Gets the recording image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to RecordingInfo /// </summary> /// <param name="recordingId">The recording identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> @@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.LiveTv Task<ImageResponseInfo> GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken); /// <summary> - /// Gets the program image asynchronous. + /// Gets the program image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ProgramInfo /// </summary> /// <param name="programId">The program identifier.</param> /// <param name="channelId">The channel identifier.</param> diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs new file mode 100644 index 000000000..1e6d74ce8 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -0,0 +1,57 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.LiveTv; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace MediaBrowser.Controller.LiveTv +{ + public class LiveTvChannel : BaseItem, IItemByName + { + public LiveTvChannel() + { + UserItemCountList = new List<ItemByNameCounts>(); + } + + /// <summary> + /// Gets the user data key. + /// </summary> + /// <returns>System.String.</returns> + public override string GetUserDataKey() + { + return GetClientTypeName() + "-" + Name; + } + + [IgnoreDataMember] + public List<ItemByNameCounts> UserItemCountList { get; set; } + + public ChannelInfo ChannelInfo { get; set; } + + public string ServiceName { get; set; } + + protected override string CreateSortName() + { + double number = 0; + + if (!string.IsNullOrEmpty(ChannelInfo.Number)) + { + double.TryParse(ChannelInfo.Number, out number); + } + + return number.ToString("000-") + (Name ?? string.Empty); + } + + public override string MediaType + { + get + { + return ChannelInfo.ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; + } + } + + public override string GetClientTypeName() + { + return "Channel"; + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs new file mode 100644 index 000000000..babd9f54c --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -0,0 +1,33 @@ +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.LiveTv +{ + public class LiveTvProgram : BaseItem + { + /// <summary> + /// Gets the user data key. + /// </summary> + /// <returns>System.String.</returns> + public override string GetUserDataKey() + { + return GetClientTypeName() + "-" + Name; + } + + public ProgramInfo ProgramInfo { get; set; } + + public string ServiceName { get; set; } + + public override string MediaType + { + get + { + return ProgramInfo.IsVideo ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio; + } + } + + public override string GetClientTypeName() + { + return "Program"; + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs new file mode 100644 index 000000000..1c453ab5a --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs @@ -0,0 +1,43 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.LiveTv; + +namespace MediaBrowser.Controller.LiveTv +{ + public class LiveTvRecording : BaseItem + { + /// <summary> + /// Gets the user data key. + /// </summary> + /// <returns>System.String.</returns> + public override string GetUserDataKey() + { + return GetClientTypeName() + "-" + Name; + } + + public RecordingInfo RecordingInfo { get; set; } + + public string ServiceName { get; set; } + + public override string MediaType + { + get + { + return RecordingInfo.ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; + } + } + + public override LocationType LocationType + { + get + { + return LocationType.Remote; + } + } + + public override string GetClientTypeName() + { + return "Recording"; + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index 15b55f50e..959f67740 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -98,10 +98,16 @@ namespace MediaBrowser.Controller.LiveTv public string EpisodeTitle { get; set; } /// <summary> - /// Set this value to true or false if it is known via program info whether there is an image or not. - /// Leave it null if the only way to determine is by requesting the image and handling the failure. + /// Supply the image path if it can be accessed directly from the file system /// </summary> - public bool? HasImage { get; set; } + /// <value>The image path.</value> + public string ImagePath { get; set; } + + /// <summary> + /// Supply the image url if it can be downloaded + /// </summary> + /// <value>The image URL.</value> + public string ImageUrl { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is movie. @@ -120,7 +126,13 @@ namespace MediaBrowser.Controller.LiveTv /// </summary> /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value> public bool IsSeries { get; set; } - + + /// <summary> + /// Gets or sets a value indicating whether this instance is video. + /// </summary> + /// <value><c>true</c> if this instance is video; otherwise, <c>false</c>.</value> + public bool IsVideo { get; set; } + public ProgramInfo() { Genres = new List<string>(); diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs index 40a53e659..ee3d94594 100644 --- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs +++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs @@ -114,10 +114,16 @@ namespace MediaBrowser.Controller.LiveTv public float? CommunityRating { get; set; } /// <summary> - /// Set this value to true or false if it is known via recording info whether there is an image or not. - /// Leave it null if the only way to determine is by requesting the image and handling the failure. + /// Supply the image path if it can be accessed directly from the file system /// </summary> - public bool? HasImage { get; set; } + /// <value>The image path.</value> + public string ImagePath { get; set; } + + /// <summary> + /// Supply the image url if it can be downloaded + /// </summary> + /// <value>The image URL.</value> + public string ImageUrl { get; set; } public RecordingInfo() { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 705a0ea68..a200f610b 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -85,6 +85,7 @@ <Compile Include="Entities\IHasAspectRatio.cs" /> <Compile Include="Entities\IHasBudget.cs" /> <Compile Include="Entities\IHasCriticRating.cs" /> + <Compile Include="Entities\IHasImages.cs" /> <Compile Include="Entities\IHasLanguage.cs" /> <Compile Include="Entities\IHasMediaStreams.cs" /> <Compile Include="Entities\IHasProductionLocations.cs" /> @@ -94,6 +95,7 @@ <Compile Include="Entities\IHasTags.cs" /> <Compile Include="Entities\IHasThemeMedia.cs" /> <Compile Include="Entities\IHasTrailers.cs" /> + <Compile Include="Entities\IHasUserData.cs" /> <Compile Include="Entities\IItemByName.cs" /> <Compile Include="Entities\ILibraryItem.cs" /> <Compile Include="Entities\ImageSourceInfo.cs" /> @@ -106,11 +108,13 @@ <Compile Include="Library\ItemUpdateType.cs" /> <Compile Include="Library\IUserDataManager.cs" /> <Compile Include="Library\UserDataSaveEventArgs.cs" /> - <Compile Include="LiveTv\Channel.cs" /> + <Compile Include="LiveTv\LiveTvChannel.cs" /> <Compile Include="LiveTv\ChannelInfo.cs" /> <Compile Include="LiveTv\ILiveTvManager.cs" /> <Compile Include="LiveTv\ILiveTvService.cs" /> <Compile Include="LiveTv\ImageResponseInfo.cs" /> + <Compile Include="LiveTv\LiveTvProgram.cs" /> + <Compile Include="LiveTv\LiveTvRecording.cs" /> <Compile Include="LiveTv\ProgramInfo.cs" /> <Compile Include="LiveTv\RecordingInfo.cs" /> <Compile Include="LiveTv\SeriesTimerInfo.cs" /> diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs index e4f4e8bbb..ced53299d 100644 --- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs +++ b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs @@ -170,7 +170,7 @@ namespace MediaBrowser.Controller.MediaInfo InputType type; - var inputPath = MediaEncoderHelpers.GetInputArgument(video, null, out type); + var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, false, video.VideoType, video.IsoType, null, video.PlayableStreamFileNames, out type); try { @@ -233,33 +233,23 @@ namespace MediaBrowser.Controller.MediaInfo /// <summary> /// Gets the subtitle cache path. /// </summary> - /// <param name="input">The input.</param> - /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param> + /// <param name="mediaPath">The media path.</param> + /// <param name="subtitleStream">The subtitle stream.</param> /// <param name="offset">The offset.</param> /// <param name="outputExtension">The output extension.</param> /// <returns>System.String.</returns> - public string GetSubtitleCachePath(Video input, int subtitleStreamIndex, TimeSpan? offset, string outputExtension) + public string GetSubtitleCachePath(string mediaPath, MediaStream subtitleStream, TimeSpan? offset, string outputExtension) { var ticksParam = offset.HasValue ? "_" + offset.Value.Ticks : ""; - var stream = _itemRepo.GetMediaStreams(new MediaStreamQuery + if (subtitleStream.IsExternal) { - ItemId = input.Id, - Index = subtitleStreamIndex - - }).FirstOrDefault(); - - if (stream == null) - { - return null; + ticksParam += _fileSystem.GetLastWriteTimeUtc(subtitleStream.Path).Ticks; } - if (stream.IsExternal) - { - ticksParam += _fileSystem.GetLastWriteTimeUtc(stream.Path).Ticks; - } + var date = _fileSystem.GetLastWriteTimeUtc(mediaPath); - var filename = (input.Id + "_" + subtitleStreamIndex.ToString(_usCulture) + "_" + input.DateModified.Ticks.ToString(_usCulture) + ticksParam).GetMD5() + outputExtension; + var filename = (mediaPath + "_" + subtitleStream.Index.ToString(_usCulture) + "_" + date.Ticks.ToString(_usCulture) + ticksParam).GetMD5() + outputExtension; var prefix = filename.Substring(0, 1); diff --git a/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs index 8c2f7c219..904ecdf93 100644 --- a/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs @@ -1,5 +1,8 @@ -using MediaBrowser.Common.MediaInfo; -using MediaBrowser.Controller.Entities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MediaBrowser.Common.MediaInfo; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -13,43 +16,47 @@ namespace MediaBrowser.Controller.MediaInfo /// <summary> /// Gets the input argument. /// </summary> - /// <param name="video">The video.</param> + /// <param name="videoPath">The video path.</param> + /// <param name="isRemote">if set to <c>true</c> [is remote].</param> + /// <param name="videoType">Type of the video.</param> + /// <param name="isoType">Type of the iso.</param> /// <param name="isoMount">The iso mount.</param> + /// <param name="playableStreamFileNames">The playable stream file names.</param> /// <param name="type">The type.</param> /// <returns>System.String[][].</returns> - public static string[] GetInputArgument(Video video, IIsoMount isoMount, out InputType type) + public static string[] GetInputArgument(string videoPath, bool isRemote, VideoType videoType, IsoType? isoType, IIsoMount isoMount, IEnumerable<string> playableStreamFileNames, out InputType type) { - var inputPath = isoMount == null ? new[] { video.Path } : new[] { isoMount.MountedPath }; + var inputPath = isoMount == null ? new[] { videoPath } : new[] { isoMount.MountedPath }; type = InputType.VideoFile; - switch (video.VideoType) + switch (videoType) { case VideoType.BluRay: type = InputType.Bluray; break; case VideoType.Dvd: type = InputType.Dvd; - inputPath = video.GetPlayableStreamFiles(inputPath[0]).ToArray(); + inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray(); break; case VideoType.Iso: - if (video.IsoType.HasValue) + if (isoType.HasValue) { - switch (video.IsoType.Value) + switch (isoType.Value) { case IsoType.BluRay: type = InputType.Bluray; break; case IsoType.Dvd: type = InputType.Dvd; - inputPath = video.GetPlayableStreamFiles(inputPath[0]).ToArray(); + inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray(); break; } } break; case VideoType.VideoFile: { - if (video.LocationType == LocationType.Remote) + if (isRemote) { type = InputType.Url; } @@ -60,6 +67,17 @@ namespace MediaBrowser.Controller.MediaInfo return inputPath; } + public static List<string> GetPlayableStreamFiles(string rootPath, IEnumerable<string> filenames) + { + var allFiles = Directory + .EnumerateFiles(rootPath, "*", SearchOption.AllDirectories) + .ToList(); + + return filenames.Select(name => allFiles.FirstOrDefault(f => string.Equals(Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase))) + .Where(f => !string.IsNullOrEmpty(f)) + .ToList(); + } + /// <summary> /// Gets the type of the input. /// </summary> diff --git a/MediaBrowser.Controller/Providers/IImageEnhancer.cs b/MediaBrowser.Controller/Providers/IImageEnhancer.cs index 54ba6d322..ae605ec0d 100644 --- a/MediaBrowser.Controller/Providers/IImageEnhancer.cs +++ b/MediaBrowser.Controller/Providers/IImageEnhancer.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="item">The item.</param> /// <param name="imageType">Type of the image.</param> /// <returns><c>true</c> if this enhancer will enhance the supplied image for the supplied item, <c>false</c> otherwise</returns> - bool Supports(BaseItem item, ImageType imageType); + bool Supports(IHasImages item, ImageType imageType); /// <summary> /// Gets the priority or order in which this enhancer should be run. @@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="item">The item.</param> /// <param name="imageType">Type of the image.</param> /// <returns>Cache key relating to the current state of this item and configuration</returns> - string GetConfigurationCacheKey(BaseItem item, ImageType imageType); + string GetConfigurationCacheKey(IHasImages item, ImageType imageType); /// <summary> /// Gets the size of the enhanced image. @@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Providers /// <param name="imageIndex">Index of the image.</param> /// <param name="originalImageSize">Size of the original image.</param> /// <returns>ImageSize.</returns> - ImageSize GetEnhancedImageSize(BaseItem item, ImageType imageType, int imageIndex, ImageSize originalImageSize); + ImageSize GetEnhancedImageSize(IHasImages item, ImageType imageType, int imageIndex, ImageSize originalImageSize); /// <summary> /// Enhances the image async. @@ -49,6 +49,6 @@ namespace MediaBrowser.Controller.Providers /// <param name="imageIndex">Index of the image.</param> /// <returns>Task{Image}.</returns> /// <exception cref="System.ArgumentNullException"></exception> - Task<Image> EnhanceImageAsync(BaseItem item, Image originalImage, ImageType imageType, int imageIndex); + Task<Image> EnhanceImageAsync(IHasImages item, Image originalImage, ImageType imageType, int imageIndex); } }
\ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 62688cdc4..58f8b4e05 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -252,8 +252,8 @@ namespace MediaBrowser.Model.Configuration EnableVideoImageExtraction = true; EnableMovieChapterImageExtraction = true; - EnableEpisodeChapterImageExtraction = true; - EnableOtherVideoChapterImageExtraction = true; + EnableEpisodeChapterImageExtraction = false; + EnableOtherVideoChapterImageExtraction = false; #if (DEBUG) EnableDeveloperTools = true; diff --git a/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs b/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs index 0c4eb28c5..7a55282cc 100644 --- a/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.LiveTv { @@ -109,6 +110,12 @@ namespace MediaBrowser.Model.LiveTv public string EpisodeTitle { get; set; } /// <summary> + /// Gets or sets the image tags. + /// </summary> + /// <value>The image tags.</value> + public Dictionary<ImageType, Guid> ImageTags { get; set; } + + /// <summary> /// Gets or sets the user data. /// </summary> /// <value>The user data.</value> @@ -132,9 +139,16 @@ namespace MediaBrowser.Model.LiveTv /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value> public bool IsSeries { get; set; } + /// <summary> + /// Gets or sets the type. + /// </summary> + /// <value>The type.</value> + public string Type { get; set; } + public ProgramInfoDto() { Genres = new List<string>(); + ImageTags = new Dictionary<ImageType, Guid>(); } } diff --git a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs index 2f836dc4d..16ced7c2c 100644 --- a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.LiveTv { @@ -137,14 +138,27 @@ namespace MediaBrowser.Model.LiveTv public ProgramAudio? Audio { get; set; } /// <summary> + /// Gets or sets the image tags. + /// </summary> + /// <value>The image tags.</value> + public Dictionary<ImageType, Guid> ImageTags { get; set; } + + /// <summary> /// Gets or sets the user data. /// </summary> /// <value>The user data.</value> public UserItemDataDto UserData { get; set; } + /// <summary> + /// Gets or sets the type. + /// </summary> + /// <value>The type.</value> + public string Type { get; set; } + public RecordingInfoDto() { Genres = new List<string>(); + ImageTags = new Dictionary<ImageType, Guid>(); } } }
\ No newline at end of file diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index 3aa94ff83..20a7074a5 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -16,6 +16,12 @@ /// </summary> /// <value>The user identifier.</value> public string UserId { get; set; } + + /// <summary> + /// Gets or sets the identifier. + /// </summary> + /// <value>The identifier.</value> + public string Id { get; set; } } public class TimerQuery diff --git a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs index 65e8afd7a..a19cd929d 100644 --- a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs @@ -212,7 +212,7 @@ namespace MediaBrowser.Providers if (image != null) { - item.SetImage(ImageType.Logo, image.FullName); + item.SetImagePath(ImageType.Logo, image.FullName); } // Clearart @@ -220,7 +220,7 @@ namespace MediaBrowser.Providers if (image != null) { - item.SetImage(ImageType.Art, image.FullName); + item.SetImagePath(ImageType.Art, image.FullName); } // Disc @@ -229,7 +229,7 @@ namespace MediaBrowser.Providers if (image != null) { - item.SetImage(ImageType.Disc, image.FullName); + item.SetImagePath(ImageType.Disc, image.FullName); } // Box Image @@ -237,7 +237,7 @@ namespace MediaBrowser.Providers if (image != null) { - item.SetImage(ImageType.Box, image.FullName); + item.SetImagePath(ImageType.Box, image.FullName); } // BoxRear Image @@ -245,7 +245,7 @@ namespace MediaBrowser.Providers if (image != null) { - item.SetImage(ImageType.BoxRear, image.FullName); + item.SetImagePath(ImageType.BoxRear, image.FullName); } // Thumbnail Image @@ -253,7 +253,7 @@ namespace MediaBrowser.Providers if (image != null) { - item.SetImage(ImageType.Menu, image.FullName); + item.SetImagePath(ImageType.Menu, image.FullName); } PopulateBanner(item, args); @@ -311,7 +311,7 @@ namespace MediaBrowser.Providers if (image != null) { - item.SetImage(ImageType.Primary, image.FullName); + item.SetImagePath(ImageType.Primary, image.FullName); } } @@ -339,7 +339,7 @@ namespace MediaBrowser.Providers if (image != null) { - item.SetImage(ImageType.Banner, image.FullName); + item.SetImagePath(ImageType.Banner, image.FullName); } } @@ -367,7 +367,7 @@ namespace MediaBrowser.Providers if (image != null) { - item.SetImage(ImageType.Thumb, image.FullName); + item.SetImagePath(ImageType.Thumb, image.FullName); } } diff --git a/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs index b0bc1b875..8ee2553d0 100644 --- a/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs +++ b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Providers.LiveTv /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> public override bool Supports(BaseItem item) { - return item is Channel; + return item is LiveTvChannel; } /// <summary> @@ -74,7 +74,7 @@ namespace MediaBrowser.Providers.LiveTv try { - new BaseItemXmlParser<Channel>(Logger).Fetch((Channel)item, path, cancellationToken); + new BaseItemXmlParser<LiveTvChannel>(Logger).Fetch((LiveTvChannel)item, path, cancellationToken); } finally { diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs index 5f285e6d8..0fdeddb49 100644 --- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs @@ -115,7 +115,7 @@ namespace MediaBrowser.Providers.MediaInfo if (video != null) { - inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type); + inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type); } return await MediaEncoder.GetMediaInfo(inputPath, type, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index b35d5e07e..d5815690f 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -253,7 +253,7 @@ namespace MediaBrowser.Providers.MediaInfo InputType type; - var inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type); + var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type); await _mediaEncoder.ExtractImage(inputPath, type, video.Video3DFormat, imageOffset, path, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs index 6880c9948..ad7f1287f 100644 --- a/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Savers // If new metadata has been downloaded or metadata was manually edited, proceed if ((wasMetadataEdited || wasMetadataDownloaded)) { - return item is Channel; + return item is LiveTvChannel; } return false; diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index 36b6e5a90..fbe78e938 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -594,7 +594,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// <param name="imagePath">The image path.</param> /// <returns>Guid.</returns> /// <exception cref="System.ArgumentNullException">item</exception> - public Guid GetImageCacheTag(BaseItem item, ImageType imageType, string imagePath) + public Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath) { if (item == null) { @@ -623,7 +623,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// <param name="imageEnhancers">The image enhancers.</param> /// <returns>Guid.</returns> /// <exception cref="System.ArgumentNullException">item</exception> - public Guid GetImageCacheTag(BaseItem item, ImageType imageType, string originalImagePath, DateTime dateModified, List<IImageEnhancer> imageEnhancers) + public Guid GetImageCacheTag(IHasImages item, ImageType imageType, string originalImagePath, DateTime dateModified, List<IImageEnhancer> imageEnhancers) { if (item == null) { @@ -660,7 +660,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// <param name="imageType">Type of the image.</param> /// <param name="imageIndex">Index of the image.</param> /// <returns>Task{System.String}.</returns> - public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex) + public async Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex) { var enhancers = GetSupportedEnhancers(item, imageType).ToList(); @@ -673,7 +673,7 @@ namespace MediaBrowser.Server.Implementations.Drawing return result.Item1; } - private async Task<Tuple<string, DateTime>> GetEnhancedImage(string originalImagePath, DateTime dateModified, BaseItem item, + private async Task<Tuple<string, DateTime>> GetEnhancedImage(string originalImagePath, DateTime dateModified, IHasImages item, ImageType imageType, int imageIndex, List<IImageEnhancer> enhancers) { @@ -709,7 +709,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// <param name="supportedEnhancers">The supported enhancers.</param> /// <returns>System.String.</returns> /// <exception cref="System.ArgumentNullException">originalImagePath</exception> - private async Task<string> GetEnhancedImageInternal(string originalImagePath, DateTime dateModified, BaseItem item, ImageType imageType, int imageIndex, List<IImageEnhancer> supportedEnhancers) + private async Task<string> GetEnhancedImageInternal(string originalImagePath, DateTime dateModified, IHasImages item, ImageType imageType, int imageIndex, List<IImageEnhancer> supportedEnhancers) { if (string.IsNullOrEmpty(originalImagePath)) { @@ -782,7 +782,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// <param name="imageType">Type of the image.</param> /// <param name="imageIndex">Index of the image.</param> /// <returns>Task{EnhancedImage}.</returns> - private async Task<Image> ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, Image originalImage, BaseItem item, ImageType imageType, int imageIndex) + private async Task<Image> ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, Image originalImage, IHasImages item, ImageType imageType, int imageIndex) { var result = originalImage; @@ -900,7 +900,7 @@ namespace MediaBrowser.Server.Implementations.Drawing return Path.Combine(path, filename); } - public IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType) + public IEnumerable<IImageEnhancer> GetSupportedEnhancers(IHasImages item, ImageType imageType) { return ImageEnhancers.Where(i => { diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 14496d362..1060886a8 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -818,7 +818,7 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.ParentLogoItemId = GetDtoId(parentWithLogo); - dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImage(ImageType.Logo)); + dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImagePath(ImageType.Logo)); } } @@ -831,7 +831,7 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.ParentArtItemId = GetDtoId(parentWithImage); - dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImage(ImageType.Art)); + dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImagePath(ImageType.Art)); } } @@ -844,7 +844,7 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.ParentThumbItemId = GetDtoId(parentWithImage); - dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb, parentWithImage.GetImage(ImageType.Thumb)); + dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb, parentWithImage.GetImagePath(ImageType.Thumb)); } } @@ -1037,7 +1037,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (series.HasImage(ImageType.Thumb)) { - dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb, series.GetImage(ImageType.Thumb)); + dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb, series.GetImagePath(ImageType.Thumb)); } var imagePath = series.PrimaryImagePath; diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs index 8d010aecc..79f126511 100644 --- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Server.Implementations.Library /// userId /// or /// key</exception> - public async Task SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken) + public async Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken) { if (userData == null) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs index 4a8b2d638..3d0cdd33f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs @@ -1,4 +1,5 @@ using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -21,18 +22,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly ILiveTvManager _liveTvManager; private readonly IProviderManager _providerManager; private readonly IFileSystem _fileSystem; + private readonly IHttpClient _httpClient; - public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem) + public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient) : base(logManager, configurationManager) { _liveTvManager = liveTvManager; _providerManager = providerManager; _fileSystem = fileSystem; + _httpClient = httpClient; } public override bool Supports(BaseItem item) { - return item is Channel; + return item is LiveTvChannel; } protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) @@ -48,21 +51,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv return true; } - var channel = (Channel)item; - - if (channel.HasProviderImage ?? true) + try { - try - { - await DownloadImage(item, cancellationToken).ConfigureAwait(false); - } - catch (HttpException ex) + await DownloadImage((LiveTvChannel)item, cancellationToken).ConfigureAwait(false); + } + catch (HttpException ex) + { + // Don't fail the provider on a 404 + if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) { - // Don't fail the provider on a 404 - if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) - { - throw; - } + throw; } } @@ -70,20 +68,55 @@ namespace MediaBrowser.Server.Implementations.LiveTv return true; } - private async Task DownloadImage(BaseItem item, CancellationToken cancellationToken) + private async Task DownloadImage(LiveTvChannel item, CancellationToken cancellationToken) { - var channel = (Channel)item; + var channelInfo = item.ChannelInfo; - var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, channel.ServiceName, StringComparison.OrdinalIgnoreCase)); + Stream imageStream = null; + string contentType = null; - if (service != null) + if (!string.IsNullOrEmpty(channelInfo.ImagePath)) { - var response = await service.GetChannelImageAsync(channel.ChannelId, cancellationToken).ConfigureAwait(false); + contentType = "image/" + Path.GetExtension(channelInfo.ImagePath).ToLower(); + imageStream = _fileSystem.GetFileStream(channelInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); + } + else if (!string.IsNullOrEmpty(channelInfo.ImageUrl)) + { + var options = new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = channelInfo.ImageUrl + }; + + var response = await _httpClient.GetResponse(options).ConfigureAwait(false); + if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("Provider did not return an image content type."); + } + + imageStream = response.Content; + contentType = response.ContentType; + } + else + { + var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase)); + + if (service != null) + { + var response = await service.GetChannelImageAsync(channelInfo.Id, cancellationToken).ConfigureAwait(false); + + imageStream = response.Stream; + contentType = response.MimeType; + } + } + + if (imageStream != null) + { // Dummy up the original url - var url = channel.ServiceName + channel.ChannelId; + var url = item.ServiceName + channelInfo.Id; - await _providerManager.SaveImage(channel, response.Stream, response.MimeType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); + await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index a58722c32..3d18e9837 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -135,11 +135,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv return pattern; } - public RecordingInfoDto GetRecordingInfoDto(RecordingInfo info, ILiveTvService service, User user = null) + /// <summary> + /// Convert the provider 0-5 scale to our 0-10 scale + /// </summary> + /// <param name="val"></param> + /// <returns></returns> + private float? GetClientCommunityRating(float? val) + { + if (!val.HasValue) + { + return null; + } + + return val.Value * 2; + } + + public RecordingInfoDto GetRecordingInfoDto(LiveTvRecording recording, ILiveTvService service, User user = null) { + var info = recording.RecordingInfo; + var dto = new RecordingInfoDto { Id = GetInternalRecordingId(service.Name, info.Id).ToString("N"), + Type = recording.GetClientTypeName(), ChannelName = info.ChannelName, Overview = info.Overview, EndDate = info.EndDate, @@ -154,7 +172,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv EpisodeTitle = info.EpisodeTitle, ChannelType = info.ChannelType, MediaType = info.ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video, - CommunityRating = info.CommunityRating, + CommunityRating = GetClientCommunityRating(info.CommunityRating), OfficialRating = info.OfficialRating, Audio = info.Audio, IsHD = info.IsHD, @@ -162,9 +180,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv Url = info.Url }; + var imageTag = GetImageTag(recording); + + if (imageTag.HasValue) + { + dto.ImageTags[ImageType.Primary] = imageTag.Value; + } + if (user != null) { - //dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey())); + dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, recording.GetUserDataKey())); } var duration = info.EndDate - info.StartDate; @@ -184,18 +209,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv /// <param name="info">The info.</param> /// <param name="user">The user.</param> /// <returns>ChannelInfoDto.</returns> - public ChannelInfoDto GetChannelInfoDto(Channel info, User user = null) + public ChannelInfoDto GetChannelInfoDto(LiveTvChannel info, User user = null) { + var channelInfo = info.ChannelInfo; + var dto = new ChannelInfoDto { Name = info.Name, ServiceName = info.ServiceName, - ChannelType = info.ChannelType, - Number = info.ChannelNumber, - Type = info.GetType().Name, + ChannelType = channelInfo.ChannelType, + Number = channelInfo.Number, + Type = info.GetClientTypeName(), Id = info.Id.ToString("N"), MediaType = info.MediaType, - ExternalId = info.ChannelId + ExternalId = channelInfo.Id }; if (user != null) @@ -203,7 +230,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey())); } - var imageTag = GetLogoImageTag(info); + var imageTag = GetImageTag(info); if (imageTag.HasValue) { @@ -213,7 +240,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return dto; } - public ProgramInfoDto GetProgramInfoDto(ProgramInfo program, Channel channel, User user = null) + public ProgramInfoDto GetProgramInfoDto(ProgramInfo program, LiveTvChannel channel, User user = null) { var dto = new ProgramInfoDto { @@ -230,7 +257,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv IsHD = program.IsHD, OriginalAirDate = program.OriginalAirDate, Audio = program.Audio, - CommunityRating = program.CommunityRating, + CommunityRating = GetClientCommunityRating(program.CommunityRating), AspectRatio = program.AspectRatio, IsRepeat = program.IsRepeat, EpisodeTitle = program.EpisodeTitle, @@ -248,7 +275,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return dto; } - private Guid? GetLogoImageTag(Channel info) + private Guid? GetImageTag(BaseItem info) { var path = info.PrimaryImagePath; @@ -263,7 +290,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv } catch (Exception ex) { - _logger.ErrorException("Error getting channel image info for {0}", ex, info.Name); + _logger.ErrorException("Error getting image info for {0}", ex, info.Name); } return null; @@ -273,7 +300,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var name = serviceName + externalId + channelName; - return name.ToLower().GetMBId(typeof(Channel)); + return name.ToLower().GetMBId(typeof(LiveTvChannel)); } public Guid GetInternalTimerId(string serviceName, string externalId) @@ -314,41 +341,53 @@ namespace MediaBrowser.Server.Implementations.LiveTv Name = dto.Name, StartDate = dto.StartDate, Status = dto.Status, - SeriesTimerId = dto.ExternalSeriesTimerId, PrePaddingSeconds = dto.PrePaddingSeconds, PostPaddingSeconds = dto.PostPaddingSeconds, IsPostPaddingRequired = dto.IsPostPaddingRequired, IsPrePaddingRequired = dto.IsPrePaddingRequired, - Priority = dto.Priority + Priority = dto.Priority, + SeriesTimerId = dto.ExternalSeriesTimerId, + ProgramId = dto.ExternalProgramId, + ChannelId = dto.ExternalChannelId, + Id = dto.ExternalId }; // Convert internal server id's to external tv provider id's - if (!isNew && !string.IsNullOrEmpty(dto.Id)) + if (!isNew && !string.IsNullOrEmpty(dto.Id) && string.IsNullOrEmpty(info.Id)) { - var timer = await liveTv.GetTimer(dto.Id, cancellationToken).ConfigureAwait(false); + var timer = await liveTv.GetSeriesTimer(dto.Id, cancellationToken).ConfigureAwait(false); info.Id = timer.ExternalId; } - if (!string.IsNullOrEmpty(dto.SeriesTimerId)) + if (!string.IsNullOrEmpty(dto.ChannelId) && string.IsNullOrEmpty(info.ChannelId)) { - var timer = await liveTv.GetSeriesTimer(dto.SeriesTimerId, cancellationToken).ConfigureAwait(false); + var channel = await liveTv.GetChannel(dto.ChannelId, cancellationToken).ConfigureAwait(false); - info.SeriesTimerId = timer.ExternalId; + if (channel != null) + { + info.ChannelId = channel.ExternalId; + } } - if (!string.IsNullOrEmpty(dto.ChannelId)) + if (!string.IsNullOrEmpty(dto.ProgramId) && string.IsNullOrEmpty(info.ProgramId)) { - var channel = await liveTv.GetChannel(dto.ChannelId, cancellationToken).ConfigureAwait(false); + var program = await liveTv.GetProgram(dto.ProgramId, cancellationToken).ConfigureAwait(false); - info.ChannelId = channel.ExternalId; + if (program != null) + { + info.ProgramId = program.ExternalId; + } } - if (!string.IsNullOrEmpty(dto.ProgramId)) + if (!string.IsNullOrEmpty(dto.SeriesTimerId) && string.IsNullOrEmpty(info.SeriesTimerId)) { - var program = await liveTv.GetProgram(dto.ProgramId, cancellationToken).ConfigureAwait(false); + var timer = await liveTv.GetSeriesTimer(dto.SeriesTimerId, cancellationToken).ConfigureAwait(false); - info.ProgramId = program.ExternalId; + if (timer != null) + { + info.SeriesTimerId = timer.ExternalId; + } } return info; @@ -371,29 +410,38 @@ namespace MediaBrowser.Server.Implementations.LiveTv Priority = dto.Priority, RecordAnyChannel = dto.RecordAnyChannel, RecordAnyTime = dto.RecordAnyTime, - RecordNewOnly = dto.RecordNewOnly + RecordNewOnly = dto.RecordNewOnly, + ProgramId = dto.ExternalProgramId, + ChannelId = dto.ExternalChannelId, + Id = dto.ExternalId }; // Convert internal server id's to external tv provider id's - if (!isNew && !string.IsNullOrEmpty(dto.Id)) + if (!isNew && !string.IsNullOrEmpty(dto.Id) && string.IsNullOrEmpty(info.Id)) { var timer = await liveTv.GetSeriesTimer(dto.Id, cancellationToken).ConfigureAwait(false); info.Id = timer.ExternalId; } - if (!string.IsNullOrEmpty(dto.ChannelId)) + if (!string.IsNullOrEmpty(dto.ChannelId) && string.IsNullOrEmpty(info.ChannelId)) { var channel = await liveTv.GetChannel(dto.ChannelId, cancellationToken).ConfigureAwait(false); - info.ChannelId = channel.ExternalId; + if (channel != null) + { + info.ChannelId = channel.ExternalId; + } } - if (!string.IsNullOrEmpty(dto.ProgramId)) + if (!string.IsNullOrEmpty(dto.ProgramId) && string.IsNullOrEmpty(info.ProgramId)) { var program = await liveTv.GetProgram(dto.ProgramId, cancellationToken).ConfigureAwait(false); - info.ProgramId = program.ExternalId; + if (program != null) + { + info.ProgramId = program.ExternalId; + } } return info; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 06ed4e200..06e6fbe15 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly List<ILiveTvService> _services = new List<ILiveTvService>(); - private List<Channel> _channels = new List<Channel>(); + private List<LiveTvChannel> _channels = new List<LiveTvChannel>(); private List<ProgramInfoDto> _programs = new List<ProgramInfoDto>(); public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager) @@ -77,18 +77,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId)); - IEnumerable<Channel> channels = _channels; + IEnumerable<LiveTvChannel> channels = _channels; if (user != null) { - channels = channels.Where(i => i.IsParentalAllowed(user, _localization)) + channels = channels + .Where(i => i.IsParentalAllowed(user, _localization)) .OrderBy(i => { double number = 0; - if (!string.IsNullOrEmpty(i.ChannelNumber)) + if (!string.IsNullOrEmpty(i.ChannelInfo.Number)) { - double.TryParse(i.ChannelNumber, out number); + double.TryParse(i.ChannelInfo.Number, out number); } return number; @@ -100,9 +101,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv { double number = 0; - if (!string.IsNullOrEmpty(i.ChannelNumber)) + if (!string.IsNullOrEmpty(i.ChannelInfo.Number)) { - double.TryParse(i.ChannelNumber, out number); + double.TryParse(i.ChannelInfo.Number, out number); } return number; @@ -120,14 +121,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv return Task.FromResult(result); } - public Channel GetChannel(string id) + public LiveTvChannel GetInternalChannel(string id) { var guid = new Guid(id); return _channels.FirstOrDefault(i => i.Id == guid); } - private async Task<Channel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken) + public async Task<LiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken) + { + var service = ActiveService; + + var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); + + var recording = recordings.FirstOrDefault(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id)); + + return await GetRecording(recording, service.Name, cancellationToken).ConfigureAwait(false); + } + + private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken) { var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name)); @@ -150,26 +162,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id, channelInfo.Name); - var item = _itemRepo.RetrieveItem(id) as Channel; + var item = _itemRepo.RetrieveItem(id) as LiveTvChannel; if (item == null) { - item = new Channel + item = new LiveTvChannel { Name = channelInfo.Name, Id = id, DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo), DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo), - Path = path, - ChannelId = channelInfo.Id, - ChannelNumber = channelInfo.Number, - ServiceName = serviceName, - HasProviderImage = channelInfo.HasImage + Path = path }; isNew = true; } + item.ChannelInfo = channelInfo; + item.ServiceName = serviceName; + // Set this now so we don't cause additional file system access during provider executions item.ResetResolveArgs(fileInfo); @@ -178,6 +189,35 @@ namespace MediaBrowser.Server.Implementations.LiveTv return item; } + private async Task<LiveTvRecording> GetRecording(RecordingInfo info, string serviceName, CancellationToken cancellationToken) + { + var isNew = false; + + var id = _tvDtoService.GetInternalRecordingId(serviceName, info.Id); + + var item = _itemRepo.RetrieveItem(id) as LiveTvRecording; + + if (item == null) + { + item = new LiveTvRecording + { + Name = info.Name, + Id = id, + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow + }; + + isNew = true; + } + + item.RecordingInfo = info; + item.ServiceName = serviceName; + + await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false); + + return item; + } + public Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null) { var program = _programs.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); @@ -225,7 +265,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var allChannels = await GetChannels(service, cancellationToken).ConfigureAwait(false); var allChannelsList = allChannels.ToList(); - var list = new List<Channel>(); + var list = new List<LiveTvChannel>(); var programs = new List<ProgramInfoDto>(); var numComplete = 0; @@ -271,26 +311,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken) { - var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId)); + var service = ActiveService; - var list = new List<RecordingInfoDto>(); + var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId)); - if (ActiveService != null) - { - var recordings = await ActiveService.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); + var list = new List<RecordingInfo>(); - var dtos = recordings.Select(i => _tvDtoService.GetRecordingInfoDto(i, ActiveService, user)); + var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); + list.AddRange(recordings); - list.AddRange(dtos); + if (!string.IsNullOrEmpty(query.ChannelId)) + { + list = list + .Where(i => _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId, i.ChannelName) == new Guid(query.ChannelId)) + .ToList(); } - if (!string.IsNullOrEmpty(query.ChannelId)) + if (!string.IsNullOrEmpty(query.Id)) { - list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId)) + list = list + .Where(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(query.Id)) .ToList(); } - var returnArray = list.OrderByDescending(i => i.StartDate) + var entities = await GetEntities(list, service.Name, cancellationToken).ConfigureAwait(false); + + var returnArray = entities + .Select(i => _tvDtoService.GetRecordingInfoDto(i, ActiveService, user)) + .OrderByDescending(i => i.StartDate) .ToArray(); return new QueryResult<RecordingInfoDto> @@ -300,6 +348,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv }; } + private Task<LiveTvRecording[]> GetEntities(IEnumerable<RecordingInfo> recordings, string serviceName, CancellationToken cancellationToken) + { + var tasks = recordings.Select(i => GetRecording(i, serviceName, cancellationToken)); + + return Task.WhenAll(tasks); + } + private IEnumerable<ILiveTvService> GetServices(string serviceName, string channelId) { IEnumerable<ILiveTvService> services = _services; @@ -404,11 +459,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var results = await GetRecordings(new RecordingQuery { - UserId = user == null ? null : user.Id.ToString("N") + UserId = user == null ? null : user.Id.ToString("N"), + Id = id }, cancellationToken).ConfigureAwait(false); - return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture)); + return results.Items.FirstOrDefault(); } public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs new file mode 100644 index 000000000..2286e3ac5 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -0,0 +1,136 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.LiveTv +{ + public class ProgramImageProvider : BaseMetadataProvider + { + private readonly ILiveTvManager _liveTvManager; + private readonly IProviderManager _providerManager; + private readonly IFileSystem _fileSystem; + private readonly IHttpClient _httpClient; + + public ProgramImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient) + : base(logManager, configurationManager) + { + _liveTvManager = liveTvManager; + _providerManager = providerManager; + _fileSystem = fileSystem; + _httpClient = httpClient; + } + + public override bool Supports(BaseItem item) + { + return item is LiveTvProgram; + } + + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + { + return !item.HasImage(ImageType.Primary); + } + + public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + { + if (item.HasImage(ImageType.Primary)) + { + SetLastRefreshed(item, DateTime.UtcNow, providerInfo); + return true; + } + + try + { + await DownloadImage((LiveTvProgram)item, cancellationToken).ConfigureAwait(false); + } + catch (HttpException ex) + { + // Don't fail the provider on a 404 + if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) + { + throw; + } + } + + SetLastRefreshed(item, DateTime.UtcNow, providerInfo); + return true; + } + + private async Task DownloadImage(LiveTvProgram item, CancellationToken cancellationToken) + { + var programInfo = item.ProgramInfo; + + Stream imageStream = null; + string contentType = null; + + if (!string.IsNullOrEmpty(programInfo.ImagePath)) + { + contentType = "image/" + Path.GetExtension(programInfo.ImagePath).ToLower(); + imageStream = _fileSystem.GetFileStream(programInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); + } + else if (!string.IsNullOrEmpty(programInfo.ImageUrl)) + { + var options = new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = programInfo.ImageUrl + }; + + var response = await _httpClient.GetResponse(options).ConfigureAwait(false); + + if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("Provider did not return an image content type."); + } + + imageStream = response.Content; + contentType = response.ContentType; + } + else + { + var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase)); + + if (service != null) + { + var response = await service.GetProgramImageAsync(programInfo.Id, programInfo.ChannelId, cancellationToken).ConfigureAwait(false); + + imageStream = response.Stream; + contentType = response.MimeType; + } + } + + if (imageStream != null) + { + // Dummy up the original url + var url = item.ServiceName + programInfo.Id; + + await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); + } + } + + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Second; } + } + + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs new file mode 100644 index 000000000..a25dfe538 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs @@ -0,0 +1,136 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.IO; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; + +namespace MediaBrowser.Server.Implementations.LiveTv +{ + public class RecordingImageProvider : BaseMetadataProvider + { + private readonly ILiveTvManager _liveTvManager; + private readonly IProviderManager _providerManager; + private readonly IFileSystem _fileSystem; + private readonly IHttpClient _httpClient; + + public RecordingImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient) + : base(logManager, configurationManager) + { + _liveTvManager = liveTvManager; + _providerManager = providerManager; + _fileSystem = fileSystem; + _httpClient = httpClient; + } + + public override bool Supports(BaseItem item) + { + return item is LiveTvRecording; + } + + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + { + return !item.HasImage(ImageType.Primary); + } + + public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken) + { + if (item.HasImage(ImageType.Primary)) + { + SetLastRefreshed(item, DateTime.UtcNow, providerInfo); + return true; + } + + try + { + await DownloadImage((LiveTvRecording)item, cancellationToken).ConfigureAwait(false); + } + catch (HttpException ex) + { + // Don't fail the provider on a 404 + if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) + { + throw; + } + } + + SetLastRefreshed(item, DateTime.UtcNow, providerInfo); + return true; + } + + private async Task DownloadImage(LiveTvRecording item, CancellationToken cancellationToken) + { + var recordingInfo = item.RecordingInfo; + + Stream imageStream = null; + string contentType = null; + + if (!string.IsNullOrEmpty(recordingInfo.ImagePath)) + { + contentType = "image/" + Path.GetExtension(recordingInfo.ImagePath).ToLower(); + imageStream = _fileSystem.GetFileStream(recordingInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); + } + else if (!string.IsNullOrEmpty(recordingInfo.ImageUrl)) + { + var options = new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = recordingInfo.ImageUrl + }; + + var response = await _httpClient.GetResponse(options).ConfigureAwait(false); + + if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("Provider did not return an image content type."); + } + + imageStream = response.Content; + contentType = response.ContentType; + } + else + { + var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase)); + + if (service != null) + { + var response = await service.GetRecordingImageAsync(recordingInfo.Id, cancellationToken).ConfigureAwait(false); + + imageStream = response.Stream; + contentType = response.MimeType; + } + } + + if (imageStream != null) + { + // Dummy up the original url + var url = item.ServiceName + recordingInfo.Id; + + await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); + } + } + + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Second; } + } + + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 2516a3ae3..5a3a9ffe9 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -153,6 +153,8 @@ <Compile Include="LiveTv\ChannelImageProvider.cs" /> <Compile Include="LiveTv\LiveTvDtoService.cs" /> <Compile Include="LiveTv\LiveTvManager.cs" /> + <Compile Include="LiveTv\ProgramImageProvider.cs" /> + <Compile Include="LiveTv\RecordingImageProvider.cs" /> <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" /> <Compile Include="Localization\LocalizationManager.cs" /> <Compile Include="MediaEncoder\MediaEncoder.cs" /> diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs index 49194ba8d..e24ed0000 100644 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs @@ -118,6 +118,8 @@ namespace MediaBrowser.Server.Implementations.Providers imageIndex = hasScreenshots.ScreenshotImagePaths.Count; } + var index = imageIndex ?? 0; + var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally); // If there are more than one output paths, the stream will need to be seekable @@ -132,7 +134,7 @@ namespace MediaBrowser.Server.Implementations.Providers source = memoryStream; } - var currentPath = GetCurrentImagePath(item, type, imageIndex); + var currentPath = GetCurrentImagePath(item, type, index); using (source) { @@ -152,7 +154,7 @@ namespace MediaBrowser.Server.Implementations.Providers } } - // Set the path into the BaseItem + // Set the path into the item SetImagePath(item, type, imageIndex, paths[0], sourceUrl); // Delete the current path @@ -257,27 +259,9 @@ namespace MediaBrowser.Server.Implementations.Providers /// or /// imageIndex /// </exception> - private string GetCurrentImagePath(BaseItem item, ImageType type, int? imageIndex) + private string GetCurrentImagePath(IHasImages item, ImageType type, int imageIndex) { - switch (type) - { - case ImageType.Screenshot: - - var hasScreenshots = (IHasScreenshots)item; - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - return hasScreenshots.ScreenshotImagePaths.Count > imageIndex.Value ? hasScreenshots.ScreenshotImagePaths[imageIndex.Value] : null; - case ImageType.Backdrop: - if (!imageIndex.HasValue) - { - throw new ArgumentNullException("imageIndex"); - } - return item.BackdropImagePaths.Count > imageIndex.Value ? item.BackdropImagePaths[imageIndex.Value] : null; - default: - return item.GetImage(type); - } + return item.GetImagePath(type, imageIndex); } /// <summary> @@ -336,7 +320,7 @@ namespace MediaBrowser.Server.Implementations.Providers } break; default: - item.SetImage(type, path); + item.SetImagePath(type, path); break; } } @@ -593,7 +577,7 @@ namespace MediaBrowser.Server.Implementations.Providers /// <param name="imageFilename">The image filename.</param> /// <param name="extension">The extension.</param> /// <returns>System.String.</returns> - private string GetSavePathForItemInMixedFolder(BaseItem item, ImageType type, string imageFilename, string extension) + private string GetSavePathForItemInMixedFolder(IHasImages item, ImageType type, string imageFilename, string extension) { if (type == ImageType.Primary) { diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs index 60c8df8c1..32df5fe13 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs @@ -96,7 +96,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks // Limit to video files to reduce changes of ffmpeg crash dialog foreach (var item in newItems .Where(i => i.LocationType == LocationType.FileSystem && i.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(i.PrimaryImagePath) && i.DefaultVideoStreamIndex.HasValue) - .Take(2)) + .Take(1)) { try { diff --git a/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs b/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs index d65665ec6..1a5d73e6b 100644 --- a/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs +++ b/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs @@ -242,19 +242,19 @@ namespace MediaBrowser.ServerApplication } if (item.HasImage(ImageType.Banner)) { - previews.Add(new PreviewItem(item.GetImage(ImageType.Banner), "Banner")); + previews.Add(new PreviewItem(item.GetImagePath(ImageType.Banner), "Banner")); } if (item.HasImage(ImageType.Logo)) { - previews.Add(new PreviewItem(item.GetImage(ImageType.Logo), "Logo")); + previews.Add(new PreviewItem(item.GetImagePath(ImageType.Logo), "Logo")); } if (item.HasImage(ImageType.Art)) { - previews.Add(new PreviewItem(item.GetImage(ImageType.Art), "Art")); + previews.Add(new PreviewItem(item.GetImagePath(ImageType.Art), "Art")); } if (item.HasImage(ImageType.Thumb)) { - previews.Add(new PreviewItem(item.GetImage(ImageType.Thumb), "Thumb")); + previews.Add(new PreviewItem(item.GetImagePath(ImageType.Thumb), "Thumb")); } previews.AddRange( item.BackdropImagePaths.Select( diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 563d08caa..a25a1592c 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.278</version> + <version>3.0.281</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.278" /> + <dependency id="MediaBrowser.Common" version="3.0.281" /> <dependency id="NLog" version="2.1.0" /> <dependency id="SimpleInjector" version="2.4.0" /> <dependency id="sharpcompress" version="0.10.2" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 10889c801..4d773e6cb 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.278</version> + <version>3.0.281</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 96e9241ab..0ac9ce4f2 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.278</version> + <version>3.0.281</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.278" /> + <dependency id="MediaBrowser.Common" version="3.0.281" /> </dependencies> </metadata> <files> |
