diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookManager.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookManager.kt index dd2499fb3..f2877531d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookManager.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookManager.kt @@ -39,4 +39,18 @@ class BookManager( }.also { logger.info { "Parsing finished in ${DurationFormatUtils.formatDurationHMS(it)}" } }) } + @Transactional + @Async("parseBookTaskExecutor") + fun regenerateThumbnailAndPersist(book: Book): Future { + logger.info { "Regenerate thumbnail and persist book: ${book.url}" } + return AsyncResult(measureTimeMillis { + try { + book.metadata = bookParser.regenerateThumbnail(book) + } catch (ex: Exception) { + logger.error(ex) { "Error while recreating thumbnail" } + book.metadata = BookMetadata(status = Status.ERROR) + } + bookRepository.save(book) + }.also { logger.info { "Thumbnail generated in ${DurationFormatUtils.formatDurationHMS(it)}" } }) + } } \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt index bd297c5a3..027a39b67 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/BookParser.kt @@ -46,24 +46,45 @@ class BookParser( logger.info { "Book has ${pages.size} pages" } logger.info { "Trying to generate cover for book: ${book.url}" } - val thumbnail = try { - ByteArrayOutputStream().use { - supportedMediaTypes.getValue(mediaType).getPageStream(book.path(), pages.first().fileName).let { cover -> - Thumbnails.of(cover.inputStream()) - .size(thumbnailSize, thumbnailSize) - .outputFormat(thumbnailFormat) - .toOutputStream(it) - it.toByteArray() - } - } - } catch (ex: Exception) { - logger.warn(ex) { "Could not generate thumbnail for book: ${book.url}" } - null - } + val thumbnail = generateThumbnail(book, mediaType, pages.first().fileName) return BookMetadata(mediaType = mediaType, status = Status.READY, pages = pages, thumbnail = thumbnail) } + fun regenerateThumbnail(book: Book): BookMetadata { + logger.info { "Regenerate thumbnail for book: ${book.url}" } + + if (book.metadata.status != Status.READY) { + logger.warn { "Book metadata is not ready, cannot generate thumbnail" } + throw MetadataNotReadyException() + } + + val thumbnail = generateThumbnail(book, book.metadata.mediaType!!, book.metadata.pages.first().fileName) + + return BookMetadata( + mediaType = book.metadata.mediaType, + status = Status.READY, + pages = book.metadata.pages, + thumbnail = thumbnail + ) + } + + private fun generateThumbnail(book: Book, mediaType: String, entry: String): ByteArray? = + try { + ByteArrayOutputStream().use { + supportedMediaTypes.getValue(mediaType).getPageStream(book.path(), entry).let { cover -> + Thumbnails.of(cover.inputStream()) + .size(thumbnailSize, thumbnailSize) + .outputFormat(thumbnailFormat) + .toOutputStream(it) + it.toByteArray() + } + } + } catch (ex: Exception) { + logger.warn(ex) { "Could not generate thumbnail for book: ${book.url}" } + null + } + fun getPageContent(book: Book, number: Int): ByteArray { logger.info { "Get page #$number for book: ${book.url}" } diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt index 08b2f082e..eb4a788d9 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/service/LibraryManager.kt @@ -94,4 +94,26 @@ class LibraryManager( logger.info { "Parsed ${booksToParse.size} books in ${DurationFormatUtils.formatDurationHMS(it)} (virtual: ${DurationFormatUtils.formatDurationHMS(sumOfTasksTime)})" } } } + + @Synchronized + fun regenerateAllThumbnails() { + logger.info { "Regenerate thumbnail for all books" } + val booksToProcess = bookRepository.findAll() + + var sumOfTasksTime = 0L + measureTimeMillis { + sumOfTasksTime = booksToProcess + .map { bookManager.regenerateThumbnailAndPersist(it) } + .map { + try { + it.get() + } catch (ex: Exception) { + 0L + } + } + .sum() + }.also { + logger.info { "Generated ${booksToProcess.size} thumbnails in ${DurationFormatUtils.formatDurationHMS(it)} (virtual: ${DurationFormatUtils.formatDurationHMS(sumOfTasksTime)})" } + } + } } \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/AdminController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/AdminController.kt new file mode 100644 index 000000000..733e463c7 --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/AdminController.kt @@ -0,0 +1,23 @@ +package org.gotson.komga.interfaces.web + +import org.gotson.komga.domain.service.LibraryManager +import org.springframework.http.HttpStatus +import org.springframework.security.access.prepost.PreAuthorize +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("api/v1/admin") +@PreAuthorize("hasRole('ROLE_ADMIN')") +class AdminController( + private val libraryManager: LibraryManager +) { + + @PostMapping("rpc/thumbnails/regenerateall") + @ResponseStatus(HttpStatus.ACCEPTED) + fun regenerateAllThumbnails() { + libraryManager.regenerateAllThumbnails() + } +} \ No newline at end of file