From 6602b0dfb6e05dadd73dd2841c579d5e9f87be59 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Jul 2020 13:17:08 -0600 Subject: Move ImageService.cs to Jellyfin.Api --- Jellyfin.Api/Controllers/ImageController.cs | 694 ++++++++++++++++++++++++---- 1 file changed, 611 insertions(+), 83 deletions(-) (limited to 'Jellyfin.Api/Controllers/ImageController.cs') diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 1322d77e9..f89601d17 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -330,7 +330,6 @@ namespace Jellyfin.Api.Controllers /// Optional. Apply a background color for transparent images. /// Optional. Apply a foreground layer on top of the image. /// Image index. - /// Enable or disable image enhancers such as cover art. /// Image stream returned. /// Item not found. /// @@ -341,6 +340,8 @@ namespace Jellyfin.Api.Controllers [HttpHead("/Items/{itemId}/Images/{imageType}")] [HttpGet("/Items/{itemId}/Images/{imageType}/{imageIndex?}")] [HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex?}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task GetItemImage( [FromRoute] Guid itemId, [FromRoute] ImageType imageType, @@ -349,17 +350,16 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? width, [FromQuery] int? height, [FromQuery] int? quality, - [FromQuery] string tag, + [FromQuery] string? tag, [FromQuery] bool? cropWhitespace, - [FromQuery] string format, - [FromQuery] bool addPlayedIndicator, + [FromQuery] string? format, + [FromQuery] bool? addPlayedIndicator, [FromQuery] double? percentPlayed, [FromQuery] int? unplayedCount, [FromQuery] int? blur, - [FromQuery] string backgroundColor, - [FromQuery] string foregroundLayer, - [FromRoute] int? imageIndex = null, - [FromQuery] bool enableImageEnhancers = true) + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromRoute] int? imageIndex = null) { var item = _libraryManager.GetItemById(itemId); if (item == null) @@ -385,7 +385,6 @@ namespace Jellyfin.Api.Controllers blur, backgroundColor, foregroundLayer, - enableImageEnhancers, item, Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) .ConfigureAwait(false); @@ -396,7 +395,84 @@ namespace Jellyfin.Api.Controllers /// /// Item id. /// Image type. + /// The maximum image width to return. + /// The maximum image height to return. + /// The fixed image width to return. + /// The fixed image height to return. + /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Optional. Supply the cache tag from the item object to receive strong caching headers. + /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. + /// Determines the output format of the image - original,gif,jpg,png. + /// Optional. Add a played indicator. + /// Optional. Percent to render for the percent played overlay. + /// Optional. Unplayed count overlay to render. + /// Optional. Blur image. + /// Optional. Apply a background color for transparent images. + /// Optional. Apply a foreground layer on top of the image. /// Image index. + /// Image stream returned. + /// Item not found. + /// + /// A containing the file stream on success, + /// or a if item not found. + /// + [HttpGet("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")] + [HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetItemImage2( + [FromRoute] Guid itemId, + [FromRoute] ImageType imageType, + [FromRoute] int? maxWidth, + [FromRoute] int? maxHeight, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromRoute] string tag, + [FromQuery] bool? cropWhitespace, + [FromRoute] string format, + [FromQuery] bool? addPlayedIndicator, + [FromRoute] double? percentPlayed, + [FromRoute] int? unplayedCount, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromRoute] int? imageIndex = null) + { + var item = _libraryManager.GetItemById(itemId); + if (item == null) + { + return NotFound(); + } + + return await GetImageInternal( + itemId, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + cropWhitespace, + addPlayedIndicator, + blur, + backgroundColor, + foregroundLayer, + item, + Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + .ConfigureAwait(false); + } + + /// + /// Get artist image by name. + /// + /// Artist name. + /// Image type. /// Optional. Supply the cache tag from the item object to receive strong caching headers. /// Determines the output format of the image - original,gif,jpg,png. /// The maximum image width to return. @@ -411,19 +487,20 @@ namespace Jellyfin.Api.Controllers /// Optional. Blur image. /// Optional. Apply a background color for transparent images. /// Optional. Apply a foreground layer on top of the image. - /// Enable or disable image enhancers such as cover art. + /// Image index. /// Image stream returned. /// Item not found. /// /// A containing the file stream on success, /// or a if item not found. /// - [HttpGet("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")] - [HttpHead("/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}")] - public ActionResult GetItemImage( - [FromRoute] Guid itemId, + [HttpGet("/Artists/{name}/Images/{imageType}/{imageIndex?}")] + [HttpHead("/Artists/{name}/Images/{imageType}/{imageIndex?}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetArtistImage( + [FromRoute] string name, [FromRoute] ImageType imageType, - [FromRoute] int? imageIndex, [FromRoute] string tag, [FromRoute] string format, [FromRoute] int? maxWidth, @@ -434,39 +511,447 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? height, [FromQuery] int? quality, [FromQuery] bool? cropWhitespace, - [FromQuery] bool addPlayedIndicator, + [FromQuery] bool? addPlayedIndicator, [FromQuery] int? blur, - [FromQuery] string backgroundColor, - [FromQuery] string foregroundLayer, - [FromQuery] bool enableImageEnhancers = true) + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromRoute] int? imageIndex = null) { - var item = _libraryManager.GetItemById(itemId); + var item = _libraryManager.GetArtist(name); if (item == null) { return NotFound(); } - return GetImageInternal( - itemId, - imageType, - imageIndex, - tag, - format, - maxWidth, - maxHeight, - percentPlayed, - unplayedCount, - width, - height, - quality, - cropWhitespace, - addPlayedIndicator, - blur, - backgroundColor, - foregroundLayer, - enableImageEnhancers, - item, - Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)); + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + cropWhitespace, + addPlayedIndicator, + blur, + backgroundColor, + foregroundLayer, + item, + Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + .ConfigureAwait(false); + } + + /// + /// Get genre image by name. + /// + /// Genre name. + /// Image type. + /// Optional. Supply the cache tag from the item object to receive strong caching headers. + /// Determines the output format of the image - original,gif,jpg,png. + /// The maximum image width to return. + /// The maximum image height to return. + /// Optional. Percent to render for the percent played overlay. + /// Optional. Unplayed count overlay to render. + /// The fixed image width to return. + /// The fixed image height to return. + /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. + /// Optional. Add a played indicator. + /// Optional. Blur image. + /// Optional. Apply a background color for transparent images. + /// Optional. Apply a foreground layer on top of the image. + /// Image index. + /// Image stream returned. + /// Item not found. + /// + /// A containing the file stream on success, + /// or a if item not found. + /// + [HttpGet("/Genres/{name}/Images/{imageType}/{imageIndex?}")] + [HttpHead("/Genres/{name}/Images/{imageType}/{imageIndex?}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetGenreImage( + [FromRoute] string name, + [FromRoute] ImageType imageType, + [FromRoute] string tag, + [FromRoute] string format, + [FromRoute] int? maxWidth, + [FromRoute] int? maxHeight, + [FromRoute] double? percentPlayed, + [FromRoute] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] bool? cropWhitespace, + [FromQuery] bool? addPlayedIndicator, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromRoute] int? imageIndex = null) + { + var item = _libraryManager.GetGenre(name); + if (item == null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + cropWhitespace, + addPlayedIndicator, + blur, + backgroundColor, + foregroundLayer, + item, + Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + .ConfigureAwait(false); + } + + /// + /// Get music genre image by name. + /// + /// Music genre name. + /// Image type. + /// Optional. Supply the cache tag from the item object to receive strong caching headers. + /// Determines the output format of the image - original,gif,jpg,png. + /// The maximum image width to return. + /// The maximum image height to return. + /// Optional. Percent to render for the percent played overlay. + /// Optional. Unplayed count overlay to render. + /// The fixed image width to return. + /// The fixed image height to return. + /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. + /// Optional. Add a played indicator. + /// Optional. Blur image. + /// Optional. Apply a background color for transparent images. + /// Optional. Apply a foreground layer on top of the image. + /// Image index. + /// Image stream returned. + /// Item not found. + /// + /// A containing the file stream on success, + /// or a if item not found. + /// + [HttpGet("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}")] + [HttpHead("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetMusicGenreImage( + [FromRoute] string name, + [FromRoute] ImageType imageType, + [FromRoute] string tag, + [FromRoute] string format, + [FromRoute] int? maxWidth, + [FromRoute] int? maxHeight, + [FromRoute] double? percentPlayed, + [FromRoute] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] bool? cropWhitespace, + [FromQuery] bool? addPlayedIndicator, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromRoute] int? imageIndex = null) + { + var item = _libraryManager.GetMusicGenre(name); + if (item == null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + cropWhitespace, + addPlayedIndicator, + blur, + backgroundColor, + foregroundLayer, + item, + Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + .ConfigureAwait(false); + } + + /// + /// Get person image by name. + /// + /// Person name. + /// Image type. + /// Optional. Supply the cache tag from the item object to receive strong caching headers. + /// Determines the output format of the image - original,gif,jpg,png. + /// The maximum image width to return. + /// The maximum image height to return. + /// Optional. Percent to render for the percent played overlay. + /// Optional. Unplayed count overlay to render. + /// The fixed image width to return. + /// The fixed image height to return. + /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. + /// Optional. Add a played indicator. + /// Optional. Blur image. + /// Optional. Apply a background color for transparent images. + /// Optional. Apply a foreground layer on top of the image. + /// Image index. + /// Image stream returned. + /// Item not found. + /// + /// A containing the file stream on success, + /// or a if item not found. + /// + [HttpGet("/Persons/{name}/Images/{imageType}/{imageIndex?}")] + [HttpHead("/Persons/{name}/Images/{imageType}/{imageIndex?}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetPersonImage( + [FromRoute] string name, + [FromRoute] ImageType imageType, + [FromRoute] string tag, + [FromRoute] string format, + [FromRoute] int? maxWidth, + [FromRoute] int? maxHeight, + [FromRoute] double? percentPlayed, + [FromRoute] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] bool? cropWhitespace, + [FromQuery] bool? addPlayedIndicator, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromRoute] int? imageIndex = null) + { + var item = _libraryManager.GetPerson(name); + if (item == null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + cropWhitespace, + addPlayedIndicator, + blur, + backgroundColor, + foregroundLayer, + item, + Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + .ConfigureAwait(false); + } + + /// + /// Get studio image by name. + /// + /// Studio name. + /// Image type. + /// Optional. Supply the cache tag from the item object to receive strong caching headers. + /// Determines the output format of the image - original,gif,jpg,png. + /// The maximum image width to return. + /// The maximum image height to return. + /// Optional. Percent to render for the percent played overlay. + /// Optional. Unplayed count overlay to render. + /// The fixed image width to return. + /// The fixed image height to return. + /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. + /// Optional. Add a played indicator. + /// Optional. Blur image. + /// Optional. Apply a background color for transparent images. + /// Optional. Apply a foreground layer on top of the image. + /// Image index. + /// Image stream returned. + /// Item not found. + /// + /// A containing the file stream on success, + /// or a if item not found. + /// + [HttpGet("/Studios/{name}/Images/{imageType}/{imageIndex?}")] + [HttpHead("/Studios/{name}/Images/{imageType}/{imageIndex?}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetStudioImage( + [FromRoute] string name, + [FromRoute] ImageType imageType, + [FromRoute] string tag, + [FromRoute] string format, + [FromRoute] int? maxWidth, + [FromRoute] int? maxHeight, + [FromRoute] double? percentPlayed, + [FromRoute] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] bool? cropWhitespace, + [FromQuery] bool? addPlayedIndicator, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromRoute] int? imageIndex = null) + { + var item = _libraryManager.GetStudio(name); + if (item == null) + { + return NotFound(); + } + + return await GetImageInternal( + item.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + cropWhitespace, + addPlayedIndicator, + blur, + backgroundColor, + foregroundLayer, + item, + Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase)) + .ConfigureAwait(false); + } + + /// + /// Get user profile image. + /// + /// User id. + /// Image type. + /// Optional. Supply the cache tag from the item object to receive strong caching headers. + /// Determines the output format of the image - original,gif,jpg,png. + /// The maximum image width to return. + /// The maximum image height to return. + /// Optional. Percent to render for the percent played overlay. + /// Optional. Unplayed count overlay to render. + /// The fixed image width to return. + /// The fixed image height to return. + /// Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases. + /// Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art. + /// Optional. Add a played indicator. + /// Optional. Blur image. + /// Optional. Apply a background color for transparent images. + /// Optional. Apply a foreground layer on top of the image. + /// Image index. + /// Image stream returned. + /// Item not found. + /// + /// A containing the file stream on success, + /// or a if item not found. + /// + [HttpGet("/Users/{userId}/Images/{imageType}/{imageIndex?}")] + [HttpHead("/Users/{userId}/Images/{imageType}/{imageIndex?}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetUserImage( + [FromRoute] Guid userId, + [FromRoute] ImageType imageType, + [FromQuery] string? tag, + [FromQuery] string? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] bool? cropWhitespace, + [FromQuery] bool? addPlayedIndicator, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromRoute] int? imageIndex = null) + { + var user = _userManager.GetUserById(userId); + if (user == null) + { + return NotFound(); + } + + var info = new ItemImageInfo + { + Path = user.ProfileImage.Path, + Type = ImageType.Profile, + DateModified = user.ProfileImage.LastModified + }; + + if (width.HasValue) + { + info.Width = width.Value; + } + + if (height.HasValue) + { + info.Height = height.Value; + } + + return await GetImageInternal( + user.Id, + imageType, + imageIndex, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + cropWhitespace, + addPlayedIndicator, + blur, + backgroundColor, + foregroundLayer, + null, + Request.Method.Equals(HttpMethods.Head, StringComparison.OrdinalIgnoreCase), + info) + .ConfigureAwait(false); } private static async Task GetMemoryStream(Stream inputStream) @@ -475,7 +960,7 @@ namespace Jellyfin.Api.Controllers var text = await reader.ReadToEndAsync().ConfigureAwait(false); var bytes = Convert.FromBase64String(text); - return new MemoryStream(bytes) {Position = 0}; + return new MemoryStream(bytes) { Position = 0 }; } private ImageInfo? GetImageInfo(BaseItem item, ItemImageInfo info, int? imageIndex) @@ -525,7 +1010,6 @@ namespace Jellyfin.Api.Controllers catch (Exception ex) { _logger.LogError(ex, "Error getting image information for {Path}", info.Path); - return null; } } @@ -534,8 +1018,8 @@ namespace Jellyfin.Api.Controllers Guid itemId, ImageType imageType, int? imageIndex, - string tag, - string format, + string? tag, + string? format, int? maxWidth, int? maxHeight, double? percentPlayed, @@ -544,13 +1028,13 @@ namespace Jellyfin.Api.Controllers int? height, int? quality, bool? cropWhitespace, - bool addPlayedIndicator, + bool? addPlayedIndicator, int? blur, - string backgroundColor, - string foregroundLayer, - bool enableImageEnhancers, - BaseItem item, - bool isHeadRequest) + string? backgroundColor, + string? foregroundLayer, + BaseItem? item, + bool isHeadRequest, + ItemImageInfo? imageInfo = null) { if (percentPlayed.HasValue) { @@ -576,16 +1060,16 @@ namespace Jellyfin.Api.Controllers unplayedCount = null; } - var imageInfo = item.GetImageInfo(imageType, imageIndex ?? 0); if (imageInfo == null) { - return NotFound(string.Format(NumberFormatInfo.InvariantInfo, "{0} does not have an image of type {1}", item.Name, imageType)); + imageInfo = item?.GetImageInfo(imageType, imageIndex ?? 0); + if (imageInfo == null) + { + return NotFound(string.Format(NumberFormatInfo.InvariantInfo, "{0} does not have an image of type {1}", item?.Name, imageType)); + } } - if (!cropWhitespace.HasValue) - { - cropWhitespace = imageType == ImageType.Logo || imageType == ImageType.Art; - } + cropWhitespace ??= imageType == ImageType.Logo || imageType == ImageType.Art; var outputFormats = GetOutputFormats(format); @@ -596,7 +1080,11 @@ namespace Jellyfin.Api.Controllers cacheDuration = TimeSpan.FromDays(365); } - var responseHeaders = new Dictionary {{"transferMode.dlna.org", "Interactive"}, {"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}}; + var responseHeaders = new Dictionary + { + { "transferMode.dlna.org", "Interactive" }, + { "realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*" } + }; return await GetImageResult( item, @@ -621,12 +1109,12 @@ namespace Jellyfin.Api.Controllers isHeadRequest).ConfigureAwait(false); } - private ImageFormat[] GetOutputFormats(string format) + private ImageFormat[] GetOutputFormats(string? format) { if (!string.IsNullOrWhiteSpace(format) && Enum.TryParse(format, true, out ImageFormat parsedFormat)) { - return new[] {parsedFormat}; + return new[] { parsedFormat }; } return GetClientSupportedFormats(); @@ -698,7 +1186,7 @@ namespace Jellyfin.Api.Controllers } private async Task GetImageResult( - BaseItem item, + BaseItem? item, Guid itemId, int? index, int? height, @@ -706,12 +1194,12 @@ namespace Jellyfin.Api.Controllers int? maxWidth, int? quality, int? width, - bool addPlayedIndicator, + bool? addPlayedIndicator, double? percentPlayed, int? unplayedCount, int? blur, - string backgroundColor, - string foregroundLayer, + string? backgroundColor, + string? foregroundLayer, ItemImageInfo imageInfo, bool cropWhitespace, IReadOnlyCollection supportedFormats, @@ -719,7 +1207,7 @@ namespace Jellyfin.Api.Controllers IDictionary headers, bool isHeadRequest) { - if (!imageInfo.IsLocalFile) + if (!imageInfo.IsLocalFile && item != null) { imageInfo = await _libraryManager.ConvertImageToLocal(item, imageInfo, index ?? 0).ConfigureAwait(false); } @@ -736,7 +1224,7 @@ namespace Jellyfin.Api.Controllers MaxWidth = maxWidth, Quality = quality ?? 100, Width = width, - AddPlayedIndicator = addPlayedIndicator, + AddPlayedIndicator = addPlayedIndicator ?? false, PercentPlayed = percentPlayed ?? 0, UnplayedCount = unplayedCount, Blur = blur, @@ -745,23 +1233,63 @@ namespace Jellyfin.Api.Controllers SupportedOutputFormats = supportedFormats }; - var imageResult = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); - - headers[HeaderNames.Vary] = HeaderNames.Accept; - /* - // TODO - return _resultFactory.GetStaticFileResult(Request, new StaticFileResultOptions - { - CacheDuration = cacheDuration, - ResponseHeaders = headers, - ContentType = imageResult.Item2, - DateLastModified = imageResult.Item3, - IsHeadRequest = isHeadRequest, - Path = imageResult.Item1, - FileShare = FileShare.Read - }); - */ - return NoContent(); + var (imagePath, imageContentType, dateImageModified) = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); + + var disableCaching = Request.Headers[HeaderNames.CacheControl].Contains("no-cache"); + var parsingSuccessful = DateTime.TryParse(Request.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader); + + // if the parsing of the IfModifiedSince header was not successful, disable caching + if (!parsingSuccessful) + { + // disableCaching = true; + } + + foreach (var (key, value) in headers) + { + Response.Headers.Add(key, value); + } + + Response.ContentType = imageContentType; + Response.Headers.Add(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture)); + Response.Headers.Add(HeaderNames.Vary, HeaderNames.Accept); + + if (disableCaching) + { + Response.Headers.Add(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate"); + Response.Headers.Add(HeaderNames.Pragma, "no-cache, no-store, must-revalidate"); + } + else + { + if (cacheDuration.HasValue) + { + Response.Headers.Add(HeaderNames.CacheControl, "public, max-age=" + cacheDuration.Value.TotalSeconds); + } + else + { + Response.Headers.Add(HeaderNames.CacheControl, "public"); + } + + Response.Headers.Add(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", new CultureInfo("en-US", false))); + + // if the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified + if (!(dateImageModified > ifModifiedSinceHeader)) + { + if (ifModifiedSinceHeader.Add(cacheDuration!.Value) < DateTime.UtcNow) + { + Response.StatusCode = StatusCodes.Status304NotModified; + return new ContentResult(); + } + } + } + + // if the request is a head request, return a NoContent result with the same headers as it would with a GET request + if (isHeadRequest) + { + return NoContent(); + } + + var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); + return File(stream, imageContentType); } } } -- cgit v1.2.3