Move ImageService.cs to Jellyfin.Api

This commit is contained in:
crobibero 2020-07-21 13:17:08 -06:00
parent 230c54721d
commit 6602b0dfb6
4 changed files with 610 additions and 754 deletions

View File

@ -330,7 +330,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
/// <param name="imageIndex">Image index.</param>
/// <param name="enableImageEnhancers">Enable or disable image enhancers such as cover art.</param>
/// <response code="200">Image stream returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>
@ -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<ActionResult> 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
/// </summary>
/// <param name="itemId">Item id.</param>
/// <param name="imageType">Image type.</param>
/// <param name="maxWidth">The maximum image width to return.</param>
/// <param name="maxHeight">The maximum image height to return.</param>
/// <param name="width">The fixed image width to return.</param>
/// <param name="height">The fixed image height to return.</param>
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
/// <param name="imageIndex">Image index.</param>
/// <response code="200">Image stream returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[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<ActionResult> 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);
}
/// <summary>
/// Get artist image by name.
/// </summary>
/// <param name="name">Artist name.</param>
/// <param name="imageType">Image type.</param>
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
/// <param name="maxWidth">The maximum image width to return.</param>
@ -411,19 +487,20 @@ namespace Jellyfin.Api.Controllers
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
/// <param name="enableImageEnhancers">Enable or disable image enhancers such as cover art.</param>
/// <param name="imageIndex">Image index.</param>
/// <response code="200">Image stream returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[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<object> 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<ActionResult> 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);
}
/// <summary>
/// Get genre image by name.
/// </summary>
/// <param name="name">Genre name.</param>
/// <param name="imageType">Image type.</param>
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
/// <param name="maxWidth">The maximum image width to return.</param>
/// <param name="maxHeight">The maximum image height to return.</param>
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
/// <param name="width">The fixed image width to return.</param>
/// <param name="height">The fixed image height to return.</param>
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
/// <param name="imageIndex">Image index.</param>
/// <response code="200">Image stream returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Genres/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/Genres/{name}/Images/{imageType}/{imageIndex?}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> 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);
}
/// <summary>
/// Get music genre image by name.
/// </summary>
/// <param name="name">Music genre name.</param>
/// <param name="imageType">Image type.</param>
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
/// <param name="maxWidth">The maximum image width to return.</param>
/// <param name="maxHeight">The maximum image height to return.</param>
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
/// <param name="width">The fixed image width to return.</param>
/// <param name="height">The fixed image height to return.</param>
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
/// <param name="imageIndex">Image index.</param>
/// <response code="200">Image stream returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/MusicGenres/{name}/Images/{imageType}/{imageIndex?}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> 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);
}
/// <summary>
/// Get person image by name.
/// </summary>
/// <param name="name">Person name.</param>
/// <param name="imageType">Image type.</param>
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
/// <param name="maxWidth">The maximum image width to return.</param>
/// <param name="maxHeight">The maximum image height to return.</param>
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
/// <param name="width">The fixed image width to return.</param>
/// <param name="height">The fixed image height to return.</param>
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
/// <param name="imageIndex">Image index.</param>
/// <response code="200">Image stream returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Persons/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/Persons/{name}/Images/{imageType}/{imageIndex?}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> 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);
}
/// <summary>
/// Get studio image by name.
/// </summary>
/// <param name="name">Studio name.</param>
/// <param name="imageType">Image type.</param>
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
/// <param name="maxWidth">The maximum image width to return.</param>
/// <param name="maxHeight">The maximum image height to return.</param>
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
/// <param name="width">The fixed image width to return.</param>
/// <param name="height">The fixed image height to return.</param>
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
/// <param name="imageIndex">Image index.</param>
/// <response code="200">Image stream returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Studios/{name}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/Studios/{name}/Images/{imageType}/{imageIndex?}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> 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);
}
/// <summary>
/// Get user profile image.
/// </summary>
/// <param name="userId">User id.</param>
/// <param name="imageType">Image type.</param>
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
/// <param name="maxWidth">The maximum image width to return.</param>
/// <param name="maxHeight">The maximum image height to return.</param>
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
/// <param name="width">The fixed image width to return.</param>
/// <param name="height">The fixed image height to return.</param>
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
/// <param name="imageIndex">Image index.</param>
/// <response code="200">Image stream returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>
/// A <see cref="FileStreamResult"/> containing the file stream on success,
/// or a <see cref="NotFoundResult"/> if item not found.
/// </returns>
[HttpGet("/Users/{userId}/Images/{imageType}/{imageIndex?}")]
[HttpHead("/Users/{userId}/Images/{imageType}/{imageIndex?}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> 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<MemoryStream> 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<string, string> {{"transferMode.dlna.org", "Interactive"}, {"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}};
var responseHeaders = new Dictionary<string, string>
{
{ "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<ActionResult> 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<ImageFormat> supportedFormats,
@ -719,7 +1207,7 @@ namespace Jellyfin.Api.Controllers
IDictionary<string, string> 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);
var (imagePath, imageContentType, dateImageModified) = await _imageProcessor.ProcessImage(options).ConfigureAwait(false);
headers[HeaderNames.Vary] = HeaderNames.Accept;
/*
// TODO
return _resultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
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)
{
CacheDuration = cacheDuration,
ResponseHeaders = headers,
ContentType = imageResult.Item2,
DateLastModified = imageResult.Item3,
IsHeadRequest = isHeadRequest,
Path = imageResult.Item1,
FileShare = FileShare.Read
});
*/
return NoContent();
// 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);
}
}
}

View File

@ -1,100 +0,0 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Services;
namespace MediaBrowser.Api.Images
{
/// <summary>
/// Class ImageRequest.
/// </summary>
public class ImageRequest : DeleteImageRequest
{
/// <summary>
/// The max width.
/// </summary>
[ApiMember(Name = "MaxWidth", Description = "The maximum image width to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxWidth { get; set; }
/// <summary>
/// The max height.
/// </summary>
[ApiMember(Name = "MaxHeight", Description = "The maximum image height to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxHeight { get; set; }
/// <summary>
/// The width.
/// </summary>
[ApiMember(Name = "Width", Description = "The fixed image width to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Width { get; set; }
/// <summary>
/// The height.
/// </summary>
[ApiMember(Name = "Height", Description = "The fixed image height to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Height { get; set; }
/// <summary>
/// Gets or sets the quality.
/// </summary>
/// <value>The quality.</value>
[ApiMember(Name = "Quality", Description = "Optional quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Quality { get; set; }
/// <summary>
/// Gets or sets the tag.
/// </summary>
/// <value>The tag.</value>
[ApiMember(Name = "Tag", Description = "Optional. Supply the cache tag from the item object to receive strong caching headers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Tag { get; set; }
[ApiMember(Name = "CropWhitespace", Description = "Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? CropWhitespace { get; set; }
[ApiMember(Name = "EnableImageEnhancers", Description = "Enable or disable image enhancers such as cover art.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool EnableImageEnhancers { get; set; }
[ApiMember(Name = "Format", Description = "Determines the output foramt of the image - original,gif,jpg,png", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public string Format { get; set; }
[ApiMember(Name = "AddPlayedIndicator", Description = "Optional. Add a played indicator", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool AddPlayedIndicator { get; set; }
[ApiMember(Name = "PercentPlayed", Description = "Optional percent to render for the percent played overlay", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public double? PercentPlayed { get; set; }
[ApiMember(Name = "UnplayedCount", Description = "Optional unplayed count overlay to render", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? UnplayedCount { get; set; }
public int? Blur { get; set; }
[ApiMember(Name = "BackgroundColor", Description = "Optional. Apply a background color for transparent images.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string BackgroundColor { get; set; }
[ApiMember(Name = "ForegroundLayer", Description = "Optional. Apply a foreground layer on top of the image.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string ForegroundLayer { get; set; }
public ImageRequest()
{
EnableImageEnhancers = true;
}
}
/// <summary>
/// Class DeleteImageRequest.
/// </summary>
public class DeleteImageRequest
{
/// <summary>
/// Gets or sets the type of the image.
/// </summary>
/// <value>The type of the image.</value>
[ApiMember(Name = "Type", Description = "Image Type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET,POST,DELETE")]
public ImageType Type { get; set; }
/// <summary>
/// Gets or sets the index.
/// </summary>
/// <value>The index.</value>
[ApiMember(Name = "Index", Description = "Image Index", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET,POST,DELETE")]
public int? Index { get; set; }
}
}

View File

@ -1,573 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using User = Jellyfin.Data.Entities.User;
namespace MediaBrowser.Api.Images
{
[Route("/Items/{Id}/Images/{Type}", "GET")]
[Route("/Items/{Id}/Images/{Type}/{Index}", "GET")]
[Route("/Items/{Id}/Images/{Type}", "HEAD")]
[Route("/Items/{Id}/Images/{Type}/{Index}", "HEAD")]
[Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}/{UnplayedCount}", "GET")]
[Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}/{UnplayedCount}", "HEAD")]
public class GetItemImage : ImageRequest
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path")]
public Guid Id { get; set; }
}
/// <summary>
/// Class GetPersonImage.
/// </summary>
[Route("/Artists/{Name}/Images/{Type}", "GET")]
[Route("/Artists/{Name}/Images/{Type}/{Index}", "GET")]
[Route("/Genres/{Name}/Images/{Type}", "GET")]
[Route("/Genres/{Name}/Images/{Type}/{Index}", "GET")]
[Route("/MusicGenres/{Name}/Images/{Type}", "GET")]
[Route("/MusicGenres/{Name}/Images/{Type}/{Index}", "GET")]
[Route("/Persons/{Name}/Images/{Type}", "GET")]
[Route("/Persons/{Name}/Images/{Type}/{Index}", "GET")]
[Route("/Studios/{Name}/Images/{Type}", "GET")]
[Route("/Studios/{Name}/Images/{Type}/{Index}", "GET")]
////[Route("/Years/{Year}/Images/{Type}", "GET")]
////[Route("/Years/{Year}/Images/{Type}/{Index}", "GET")]
[Route("/Artists/{Name}/Images/{Type}", "HEAD")]
[Route("/Artists/{Name}/Images/{Type}/{Index}", "HEAD")]
[Route("/Genres/{Name}/Images/{Type}", "HEAD")]
[Route("/Genres/{Name}/Images/{Type}/{Index}", "HEAD")]
[Route("/MusicGenres/{Name}/Images/{Type}", "HEAD")]
[Route("/MusicGenres/{Name}/Images/{Type}/{Index}", "HEAD")]
[Route("/Persons/{Name}/Images/{Type}", "HEAD")]
[Route("/Persons/{Name}/Images/{Type}/{Index}", "HEAD")]
[Route("/Studios/{Name}/Images/{Type}", "HEAD")]
[Route("/Studios/{Name}/Images/{Type}/{Index}", "HEAD")]
////[Route("/Years/{Year}/Images/{Type}", "HEAD")]
////[Route("/Years/{Year}/Images/{Type}/{Index}", "HEAD")]
public class GetItemByNameImage : ImageRequest
{
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
[ApiMember(Name = "Name", Description = "Item name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Name { get; set; }
}
/// <summary>
/// Class GetUserImage.
/// </summary>
[Route("/Users/{Id}/Images/{Type}", "GET")]
[Route("/Users/{Id}/Images/{Type}/{Index}", "GET")]
[Route("/Users/{Id}/Images/{Type}", "HEAD")]
[Route("/Users/{Id}/Images/{Type}/{Index}", "HEAD")]
public class GetUserImage : ImageRequest
{
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
[ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public Guid Id { get; set; }
}
/// <summary>
/// Class ImageService.
/// </summary>
public class ImageService : BaseApiService
{
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
private readonly IProviderManager _providerManager;
private readonly IImageProcessor _imageProcessor;
private readonly IFileSystem _fileSystem;
private readonly IAuthorizationContext _authContext;
/// <summary>
/// Initializes a new instance of the <see cref="ImageService" /> class.
/// </summary>
public ImageService(
ILogger<ImageService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
IUserManager userManager,
ILibraryManager libraryManager,
IProviderManager providerManager,
IImageProcessor imageProcessor,
IFileSystem fileSystem,
IAuthorizationContext authContext)
: base(logger, serverConfigurationManager, httpResultFactory)
{
_userManager = userManager;
_libraryManager = libraryManager;
_providerManager = providerManager;
_imageProcessor = imageProcessor;
_fileSystem = fileSystem;
_authContext = authContext;
}
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Get(GetItemImage request)
{
return GetImage(request, request.Id, null, false);
}
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Head(GetItemImage request)
{
return GetImage(request, request.Id, null, true);
}
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Get(GetUserImage request)
{
var item = _userManager.GetUserById(request.Id);
return GetImage(request, item, false);
}
public object Head(GetUserImage request)
{
var item = _userManager.GetUserById(request.Id);
return GetImage(request, item, true);
}
public object Get(GetItemByNameImage request)
{
var type = GetPathValue(0).ToString();
var item = GetItemByName(request.Name, type, _libraryManager, new DtoOptions(false));
return GetImage(request, item.Id, item, false);
}
public object Head(GetItemByNameImage request)
{
var type = GetPathValue(0).ToString();
var item = GetItemByName(request.Name, type, _libraryManager, new DtoOptions(false));
return GetImage(request, item.Id, item, true);
}
/// <summary>
/// Gets the image.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="item">The item.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>System.Object.</returns>
/// <exception cref="ResourceNotFoundException"></exception>
public Task<object> GetImage(ImageRequest request, Guid itemId, BaseItem item, bool isHeadRequest)
{
if (request.PercentPlayed.HasValue)
{
if (request.PercentPlayed.Value <= 0)
{
request.PercentPlayed = null;
}
else if (request.PercentPlayed.Value >= 100)
{
request.PercentPlayed = null;
request.AddPlayedIndicator = true;
}
}
if (request.PercentPlayed.HasValue)
{
request.UnplayedCount = null;
}
if (request.UnplayedCount.HasValue
&& request.UnplayedCount.Value <= 0)
{
request.UnplayedCount = null;
}
if (item == null)
{
item = _libraryManager.GetItemById(itemId);
if (item == null)
{
throw new ResourceNotFoundException(string.Format("Item {0} not found.", itemId.ToString("N", CultureInfo.InvariantCulture)));
}
}
var imageInfo = GetImageInfo(request, item);
if (imageInfo == null)
{
throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type));
}
bool cropWhitespace;
if (request.CropWhitespace.HasValue)
{
cropWhitespace = request.CropWhitespace.Value;
}
else
{
cropWhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art;
}
var outputFormats = GetOutputFormats(request);
TimeSpan? cacheDuration = null;
if (!string.IsNullOrEmpty(request.Tag))
{
cacheDuration = TimeSpan.FromDays(365);
}
var responseHeaders = new Dictionary<string, string>
{
{"transferMode.dlna.org", "Interactive"},
{"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}
};
return GetImageResult(
item,
itemId,
request,
imageInfo,
cropWhitespace,
outputFormats,
cacheDuration,
responseHeaders,
isHeadRequest);
}
public Task<object> GetImage(ImageRequest request, User user, bool isHeadRequest)
{
var imageInfo = GetImageInfo(request, user);
TimeSpan? cacheDuration = null;
if (!string.IsNullOrEmpty(request.Tag))
{
cacheDuration = TimeSpan.FromDays(365);
}
var responseHeaders = new Dictionary<string, string>
{
{"transferMode.dlna.org", "Interactive"},
{"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}
};
var outputFormats = GetOutputFormats(request);
return GetImageResult(user.Id,
request,
imageInfo,
outputFormats,
cacheDuration,
responseHeaders,
isHeadRequest);
}
private async Task<object> GetImageResult(
Guid itemId,
ImageRequest request,
ItemImageInfo info,
IReadOnlyCollection<ImageFormat> supportedFormats,
TimeSpan? cacheDuration,
IDictionary<string, string> headers,
bool isHeadRequest)
{
info.Type = ImageType.Profile;
var options = new ImageProcessingOptions
{
CropWhiteSpace = true,
Height = request.Height,
ImageIndex = request.Index ?? 0,
Image = info,
Item = null, // Hack alert
ItemId = itemId,
MaxHeight = request.MaxHeight,
MaxWidth = request.MaxWidth,
Quality = request.Quality ?? 100,
Width = request.Width,
AddPlayedIndicator = request.AddPlayedIndicator,
PercentPlayed = 0,
UnplayedCount = request.UnplayedCount,
Blur = request.Blur,
BackgroundColor = request.BackgroundColor,
ForegroundLayer = request.ForegroundLayer,
SupportedOutputFormats = supportedFormats
};
var imageResult = await _imageProcessor.ProcessImage(options).ConfigureAwait(false);
headers[HeaderNames.Vary] = HeaderNames.Accept;
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
CacheDuration = cacheDuration,
ResponseHeaders = headers,
ContentType = imageResult.Item2,
DateLastModified = imageResult.Item3,
IsHeadRequest = isHeadRequest,
Path = imageResult.Item1,
FileShare = FileShare.Read
}).ConfigureAwait(false);
}
private async Task<object> GetImageResult(
BaseItem item,
Guid itemId,
ImageRequest request,
ItemImageInfo image,
bool cropwhitespace,
IReadOnlyCollection<ImageFormat> supportedFormats,
TimeSpan? cacheDuration,
IDictionary<string, string> headers,
bool isHeadRequest)
{
if (!image.IsLocalFile)
{
item ??= _libraryManager.GetItemById(itemId);
image = await _libraryManager.ConvertImageToLocal(item, image, request.Index ?? 0).ConfigureAwait(false);
}
var options = new ImageProcessingOptions
{
CropWhiteSpace = cropwhitespace,
Height = request.Height,
ImageIndex = request.Index ?? 0,
Image = image,
Item = item,
ItemId = itemId,
MaxHeight = request.MaxHeight,
MaxWidth = request.MaxWidth,
Quality = request.Quality ?? 100,
Width = request.Width,
AddPlayedIndicator = request.AddPlayedIndicator,
PercentPlayed = request.PercentPlayed ?? 0,
UnplayedCount = request.UnplayedCount,
Blur = request.Blur,
BackgroundColor = request.BackgroundColor,
ForegroundLayer = request.ForegroundLayer,
SupportedOutputFormats = supportedFormats
};
var imageResult = await _imageProcessor.ProcessImage(options).ConfigureAwait(false);
headers[HeaderNames.Vary] = HeaderNames.Accept;
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
CacheDuration = cacheDuration,
ResponseHeaders = headers,
ContentType = imageResult.Item2,
DateLastModified = imageResult.Item3,
IsHeadRequest = isHeadRequest,
Path = imageResult.Item1,
FileShare = FileShare.Read
}).ConfigureAwait(false);
}
private ImageFormat[] GetOutputFormats(ImageRequest request)
{
if (!string.IsNullOrWhiteSpace(request.Format)
&& Enum.TryParse(request.Format, true, out ImageFormat format))
{
return new[] { format };
}
return GetClientSupportedFormats();
}
private ImageFormat[] GetClientSupportedFormats()
{
var supportedFormats = Request.AcceptTypes ?? Array.Empty<string>();
if (supportedFormats.Length > 0)
{
for (int i = 0; i < supportedFormats.Length; i++)
{
int index = supportedFormats[i].IndexOf(';');
if (index != -1)
{
supportedFormats[i] = supportedFormats[i].Substring(0, index);
}
}
}
var acceptParam = Request.QueryString["accept"];
var supportsWebP = SupportsFormat(supportedFormats, acceptParam, "webp", false);
if (!supportsWebP)
{
var userAgent = Request.UserAgent ?? string.Empty;
if (userAgent.IndexOf("crosswalk", StringComparison.OrdinalIgnoreCase) != -1 &&
userAgent.IndexOf("android", StringComparison.OrdinalIgnoreCase) != -1)
{
supportsWebP = true;
}
}
var formats = new List<ImageFormat>(4);
if (supportsWebP)
{
formats.Add(ImageFormat.Webp);
}
formats.Add(ImageFormat.Jpg);
formats.Add(ImageFormat.Png);
if (SupportsFormat(supportedFormats, acceptParam, "gif", true))
{
formats.Add(ImageFormat.Gif);
}
return formats.ToArray();
}
private bool SupportsFormat(IEnumerable<string> requestAcceptTypes, string acceptParam, string format, bool acceptAll)
{
var mimeType = "image/" + format;
if (requestAcceptTypes.Contains(mimeType))
{
return true;
}
if (acceptAll && requestAcceptTypes.Contains("*/*"))
{
return true;
}
return string.Equals(Request.QueryString["accept"], format, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Gets the image path.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
private static ItemImageInfo GetImageInfo(ImageRequest request, BaseItem item)
{
var index = request.Index ?? 0;
return item.GetImageInfo(request.Type, index);
}
private static ItemImageInfo GetImageInfo(ImageRequest request, User user)
{
var info = new ItemImageInfo
{
Path = user.ProfileImage.Path,
Type = ImageType.Primary,
DateModified = user.ProfileImage.LastModified,
};
if (request.Width.HasValue)
{
info.Width = request.Width.Value;
}
if (request.Height.HasValue)
{
info.Height = request.Height.Value;
}
return info;
}
/// <summary>
/// Posts the image.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="inputStream">The input stream.</param>
/// <param name="imageType">Type of the image.</param>
/// <param name="mimeType">Type of the MIME.</param>
/// <returns>Task.</returns>
public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
{
var memoryStream = await GetMemoryStream(inputStream);
// Handle image/png; charset=utf-8
mimeType = mimeType.Split(';').FirstOrDefault();
await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
}
private static async Task<MemoryStream> GetMemoryStream(Stream inputStream)
{
using var reader = new StreamReader(inputStream);
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
var bytes = Convert.FromBase64String(text);
return new MemoryStream(bytes)
{
Position = 0
};
}
private async Task PostImage(User user, Stream inputStream, string mimeType)
{
var memoryStream = await GetMemoryStream(inputStream);
// Handle image/png; charset=utf-8
mimeType = mimeType.Split(';').FirstOrDefault();
var userDataPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username);
if (user.ProfileImage != null)
{
_userManager.ClearProfileImage(user);
}
user.ProfileImage = new Jellyfin.Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
await _providerManager
.SaveImage(user, memoryStream, mimeType, user.ProfileImage.Path)
.ConfigureAwait(false);
await _userManager.UpdateUserAsync(user);
}
}
}

View File

@ -12,6 +12,7 @@
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
<Compile Remove="Images\ImageService.cs" />
</ItemGroup>
<PropertyGroup>