mirror of
https://github.com/gotson/komga.git
synced 2026-05-08 12:35:30 +02:00
feat(api): on-th-fly thumbnail generation for any page
This commit is contained in:
parent
ec06955e22
commit
7167f3ea24
3 changed files with 80 additions and 25 deletions
|
|
@ -62,10 +62,20 @@ class BookLifecycle(
|
||||||
MediaNotReadyException::class,
|
MediaNotReadyException::class,
|
||||||
IndexOutOfBoundsException::class
|
IndexOutOfBoundsException::class
|
||||||
)
|
)
|
||||||
fun getBookPage(book: Book, number: Int, convertTo: ImageType? = null): BookPageContent {
|
fun getBookPage(book: Book, number: Int, convertTo: ImageType? = null, resizeTo: Int? = null): BookPageContent {
|
||||||
val pageContent = bookAnalyzer.getPageContent(book, number)
|
val pageContent = bookAnalyzer.getPageContent(book, number)
|
||||||
val pageMediaType = book.media.pages[number - 1].mediaType
|
val pageMediaType = book.media.pages[number - 1].mediaType
|
||||||
|
|
||||||
|
if (resizeTo != null) {
|
||||||
|
val targetFormat = ImageType.JPEG
|
||||||
|
val convertedPage = try {
|
||||||
|
imageConverter.resizeImage(pageContent, targetFormat.imageIOFormat, resizeTo)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error(e) { "Resize page #$number of book $book to $resizeTo: failed" }
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
return BookPageContent(number, convertedPage, targetFormat.mediaType)
|
||||||
|
} else {
|
||||||
convertTo?.let {
|
convertTo?.let {
|
||||||
val msg = "Convert page #$number of book $book from $pageMediaType to ${it.mediaType}"
|
val msg = "Convert page #$number of book $book from $pageMediaType to ${it.mediaType}"
|
||||||
if (!imageConverter.supportedReadMediaTypes.contains(pageMediaType)) {
|
if (!imageConverter.supportedReadMediaTypes.contains(pageMediaType)) {
|
||||||
|
|
@ -91,4 +101,5 @@ class BookLifecycle(
|
||||||
|
|
||||||
return BookPageContent(number, pageContent, pageMediaType)
|
return BookPageContent(number, pageContent, pageMediaType)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.gotson.komga.infrastructure.image
|
package org.gotson.komga.infrastructure.image
|
||||||
|
|
||||||
import mu.KotlinLogging
|
import mu.KotlinLogging
|
||||||
|
import net.coobird.thumbnailator.Thumbnails
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import javax.imageio.ImageIO
|
import javax.imageio.ImageIO
|
||||||
|
|
@ -28,4 +29,13 @@ class ImageConverter {
|
||||||
ImageIO.write(image, format, it)
|
ImageIO.write(image, format, it)
|
||||||
it.toByteArray()
|
it.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun resizeImage(imageBytes: ByteArray, format: String, size: Int): ByteArray =
|
||||||
|
ByteArrayOutputStream().use {
|
||||||
|
Thumbnails.of(imageBytes.inputStream())
|
||||||
|
.size(size, size)
|
||||||
|
.outputFormat(format)
|
||||||
|
.toOutputStream(it)
|
||||||
|
it.toByteArray()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,40 @@ class BookController(
|
||||||
}
|
}
|
||||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
|
||||||
|
@GetMapping("api/v1/books/{bookId}/pages/{pageNumber}/thumbnail")
|
||||||
|
fun getBookPageThumbnail(
|
||||||
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
|
request: WebRequest,
|
||||||
|
@PathVariable bookId: Long,
|
||||||
|
@PathVariable pageNumber: Int
|
||||||
|
): ResponseEntity<ByteArray> =
|
||||||
|
bookRepository.findByIdOrNull((bookId))?.let { book ->
|
||||||
|
if (request.checkNotModified(getBookLastModified(book))) {
|
||||||
|
return@let ResponseEntity
|
||||||
|
.status(HttpStatus.NOT_MODIFIED)
|
||||||
|
.setNotModified(book)
|
||||||
|
.body(ByteArray(0))
|
||||||
|
}
|
||||||
|
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
|
||||||
|
try {
|
||||||
|
val pageContent = bookLifecycle.getBookPage(book, pageNumber, resizeTo = 300)
|
||||||
|
|
||||||
|
ResponseEntity.ok()
|
||||||
|
.contentType(getMediaTypeOrDefault(pageContent.mediaType))
|
||||||
|
.setNotModified(book)
|
||||||
|
.body(pageContent.content)
|
||||||
|
} catch (ex: IndexOutOfBoundsException) {
|
||||||
|
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Page number does not exist")
|
||||||
|
} catch (ex: ImageConversionException) {
|
||||||
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, ex.message)
|
||||||
|
} catch (ex: MediaNotReadyException) {
|
||||||
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book analysis failed")
|
||||||
|
} catch (ex: NoSuchFileException) {
|
||||||
|
logger.warn(ex) { "File not found: $book" }
|
||||||
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "File not found, it may have moved")
|
||||||
|
}
|
||||||
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
|
||||||
@PostMapping("api/v1/books/{bookId}/analyze")
|
@PostMapping("api/v1/books/{bookId}/analyze")
|
||||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue