-
-
Notifications
You must be signed in to change notification settings - Fork 545
Feature/cbz conversion with resolution option #2580
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6b02fa4
90d599c
2117aa7
27d96f6
74fa38b
53f448f
169fa55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| package org.booklore.controller; | ||
|
|
||
| import org.booklore.config.security.annotation.CheckBookAccess; | ||
| import org.booklore.model.dto.request.CbzConversionRequest; | ||
| import org.booklore.model.dto.response.CbzConversionResponse; | ||
| import org.booklore.model.enums.EReaderProfile; | ||
| import org.booklore.service.conversion.StandaloneCbzConversionService; | ||
| import com.github.junrar.exception.RarException; | ||
| import freemarker.template.TemplateException; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.access.prepost.PreAuthorize; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| /** | ||
| * REST controller for standalone CBZ to EPUB conversion. | ||
| * Allows users to convert comic book archives to EPUB format with custom resolution settings. | ||
| */ | ||
| @Tag(name = "Book Conversion", description = "Endpoints for converting books between formats") | ||
| @Slf4j | ||
| @RestController | ||
| @RequestMapping("/api/v1/conversion") | ||
| @RequiredArgsConstructor | ||
| public class BookConversionController { | ||
|
|
||
| private final StandaloneCbzConversionService conversionService; | ||
|
|
||
| /** | ||
| * Get available e-reader device profiles | ||
| */ | ||
| @Operation(summary = "Get device profiles", | ||
| description = "Retrieve list of available e-reader device profiles with their screen resolutions.") | ||
| @ApiResponse(responseCode = "200", description = "Device profiles retrieved successfully") | ||
| @GetMapping("/profiles") | ||
| public ResponseEntity<List<DeviceProfileDto>> getDeviceProfiles() { | ||
| List<DeviceProfileDto> profiles = Arrays.stream(EReaderProfile.values()) | ||
| .map(profile -> new DeviceProfileDto( | ||
| profile.name(), | ||
| profile.getDisplayName(), | ||
| profile.getWidth(), | ||
| profile.getHeight(), | ||
| profile.supportsCustomResolution() | ||
| )) | ||
| .collect(Collectors.toList()); | ||
|
|
||
| return ResponseEntity.ok(profiles); | ||
| } | ||
|
|
||
| /** | ||
| * Convert CBZ to EPUB with specified resolution | ||
| */ | ||
| @Operation(summary = "Convert CBZ to EPUB", | ||
| description = "Convert a CBZ/CBR/CB7 book to EPUB format with custom resolution settings. " + | ||
| "The converted EPUB will be created as a new book in the same library. " + | ||
| "Requires library management permission or admin.") | ||
| @ApiResponses({ | ||
| @ApiResponse(responseCode = "200", description = "Conversion completed successfully"), | ||
| @ApiResponse(responseCode = "400", description = "Invalid request or book is not CBX format"), | ||
| @ApiResponse(responseCode = "403", description = "Forbidden - requires library management permission"), | ||
| @ApiResponse(responseCode = "404", description = "Book not found") | ||
| }) | ||
| @PostMapping("/cbz-to-epub") | ||
| @PreAuthorize("@securityUtil.canManageLibrary() or @securityUtil.isAdmin()") | ||
| @CheckBookAccess(bookIdParam = "request.bookId", checkFromRequestBody = true) | ||
| public ResponseEntity<CbzConversionResponse> convertCbzToEpub( | ||
| @Parameter(description = "Conversion request with device profile and optional custom dimensions") | ||
| @RequestBody @Valid CbzConversionRequest request) throws IOException, TemplateException, RarException { | ||
|
|
||
| log.info("Received CBZ to EPUB conversion request for book {}", request.getBookId()); | ||
|
|
||
| CbzConversionResponse response = conversionService.convertCbzToEpub(request); | ||
|
|
||
| log.info("Successfully converted CBZ to EPUB: {}", response.getFileName()); | ||
|
|
||
| return ResponseEntity.ok(response); | ||
| } | ||
|
|
||
| /** | ||
| * DTO for device profile information | ||
| */ | ||
| public record DeviceProfileDto( | ||
| String value, | ||
| String displayName, | ||
| int width, | ||
| int height, | ||
| boolean supportsCustom | ||
| ) {} | ||
| } | ||
|
Comment on lines
+36
to
+100
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package org.booklore.model.dto.request; | ||
|
|
||
| import org.booklore.model.enums.EReaderProfile; | ||
| import jakarta.validation.constraints.Min; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import lombok.Data; | ||
|
|
||
| /** | ||
| * Request DTO for converting CBZ files to EPUB with custom resolution settings. | ||
| */ | ||
| @Data | ||
| public class CbzConversionRequest { | ||
|
|
||
| @NotNull(message = "Book ID is required") | ||
| private Long bookId; | ||
|
|
||
| @NotNull(message = "Device profile is required") | ||
| private EReaderProfile deviceProfile; | ||
|
|
||
| /** | ||
| * Custom width in pixels (optional, only used when deviceProfile is OTHER) | ||
| */ | ||
| @Min(value = 100, message = "Custom width must be at least 100 pixels") | ||
| private Integer customWidth; | ||
|
|
||
| /** | ||
| * Custom height in pixels (optional, only used when deviceProfile is OTHER) | ||
| */ | ||
| @Min(value = 100, message = "Custom height must be at least 100 pixels") | ||
| private Integer customHeight; | ||
|
Comment on lines
+23
to
+30
|
||
|
|
||
| /** | ||
| * Image compression percentage (1-100) | ||
| * Default is 85% to match existing Kobo conversion setting | ||
| */ | ||
| @Min(value = 1, message = "Compression percentage must be between 1 and 100") | ||
| private Integer compressionPercentage = 85; | ||
|
Comment on lines
+36
to
+37
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| package org.booklore.model.dto.response; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Data; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| /** | ||
| * Response DTO for CBZ to EPUB conversion result. | ||
| */ | ||
| @Data | ||
| @Builder | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class CbzConversionResponse { | ||
|
|
||
| /** | ||
| * ID of the newly created EPUB book | ||
| */ | ||
| private Long newBookId; | ||
|
|
||
| /** | ||
| * Filename of the converted EPUB | ||
| */ | ||
| private String fileName; | ||
|
|
||
| /** | ||
| * File size in kilobytes | ||
| */ | ||
| private Long fileSizeKb; | ||
|
|
||
| /** | ||
| * Target width used for conversion | ||
| */ | ||
| private Integer targetWidth; | ||
|
|
||
| /** | ||
| * Target height used for conversion | ||
| */ | ||
| private Integer targetHeight; | ||
|
|
||
| /** | ||
| * Number of pages converted | ||
| */ | ||
| private Integer pageCount; | ||
|
|
||
| /** | ||
| * Conversion success message | ||
| */ | ||
| private String message; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package org.booklore.model.enums; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| /** | ||
| * E-reader device profiles with screen resolutions for CBZ to EPUB conversion. | ||
| * Based on KCC (Kindle Comic Converter) device profiles. | ||
| */ | ||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum EReaderProfile { | ||
| // Kindle devices | ||
| KINDLE("Kindle", 600, 800), | ||
| KINDLE_DX("Kindle DX", 824, 1200), | ||
| KINDLE_PAPERWHITE("Kindle Paperwhite", 1072, 1448), | ||
| KINDLE_PAPERWHITE_5("Kindle Paperwhite 5", 1236, 1648), | ||
| KINDLE_VOYAGE("Kindle Voyage", 1072, 1448), | ||
| KINDLE_OASIS("Kindle Oasis", 1264, 1680), | ||
| KINDLE_SCRIBE("Kindle Scribe", 1860, 2480), | ||
|
|
||
| // Kobo devices | ||
| KOBO_AURA("Kobo Aura", 1404, 1872), | ||
| KOBO_AURA_HD("Kobo Aura HD", 1080, 1440), | ||
| KOBO_AURA_H2O("Kobo Aura H2O", 1080, 1430), | ||
| KOBO_AURA_ONE("Kobo Aura ONE", 1404, 1872), | ||
| KOBO_CLARA("Kobo Clara", 1072, 1448), | ||
| KOBO_CLARA_HD("Kobo Clara HD", 1072, 1448), | ||
| KOBO_ELIPSA("Kobo Elipsa", 1404, 1872), | ||
| KOBO_FORMA("Kobo Forma", 1440, 1920), | ||
| KOBO_GLO("Kobo Glo", 1024, 768), | ||
| KOBO_GLO_HD("Kobo Glo HD", 1072, 1448), | ||
| KOBO_LIBRA("Kobo Libra", 1264, 1680), | ||
| KOBO_LIBRA_H2O("Kobo Libra H2O", 1264, 1680), | ||
| KOBO_NIA("Kobo Nia", 758, 1024), | ||
| KOBO_SAGE("Kobo Sage", 1440, 1920), | ||
|
|
||
| // Other devices | ||
| NOOK_SIMPLE_TOUCH("Nook Simple Touch", 600, 800), | ||
| NOOK_GLOWLIGHT("Nook GlowLight", 1024, 758), | ||
| NOOK_GLOWLIGHT_PLUS("Nook GlowLight Plus", 1072, 1448), | ||
| SONY_PRS_T3("Sony PRS-T3", 758, 1024), | ||
| TOLINO_SHINE("Tolino Shine", 758, 1024), | ||
| TOLINO_VISION("Tolino Vision", 1072, 1448), | ||
| POCKETBOOK_TOUCH_HD("PocketBook Touch HD", 1072, 1448), | ||
| POCKETBOOK_INKPAD("PocketBook InkPad", 1200, 1600), | ||
|
|
||
| // Generic profiles | ||
| OTHER("Other (specify custom)", 0, 0); | ||
|
|
||
| private final String displayName; | ||
| private final int width; | ||
| private final int height; | ||
|
|
||
| public boolean supportsCustomResolution() { | ||
| return this == OTHER; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent package naming: The new classes use 'org.booklore' as the base package, while the entire existing codebase uses 'com.adityachandel.booklore'. This creates a package structure inconsistency that could cause confusion and maintainability issues. All new code should follow the established 'com.adityachandel.booklore' package convention.