mirror of
https://github.com/booklore-app/booklore.git
synced 2025-12-23 22:28:11 -05:00
Feat/conversion CBX to EPUB compression configuration (#1844)
* feat(conversion): add image compression percentage setting for CBX to EPUB conversion * feat(conversion): add conversion image compression setting to kobo sync settings frontend
This commit is contained in:
@@ -11,4 +11,5 @@ public class KoboSettings {
|
||||
private boolean convertCbxToEpub;
|
||||
private int conversionLimitInMbForCbx;
|
||||
private boolean forceEnableHyphenation;
|
||||
private int conversionImageCompressionPercentage;
|
||||
}
|
||||
|
||||
@@ -255,6 +255,7 @@ public class SettingPersistenceHelper {
|
||||
.conversionLimitInMb(100)
|
||||
.convertCbxToEpub(false)
|
||||
.conversionLimitInMbForCbx(100)
|
||||
.conversionImageCompressionPercentage(85)
|
||||
.forceEnableHyphenation(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ public class BookDownloadService {
|
||||
boolean convertEpubToKepub = isEpub && koboSettings.isConvertToKepub() && bookEntity.getFileSizeKb() <= (long) koboSettings.getConversionLimitInMb() * 1024;
|
||||
boolean convertCbxToEpub = isCbx && koboSettings.isConvertCbxToEpub() && bookEntity.getFileSizeKb() <= (long) koboSettings.getConversionLimitInMbForCbx() * 1024;
|
||||
|
||||
int compressionPercentage = koboSettings.getConversionImageCompressionPercentage();
|
||||
Path tempDir = null;
|
||||
try {
|
||||
File inputFile = new File(FileUtils.getBookFullPath(bookEntity));
|
||||
@@ -106,7 +107,7 @@ public class BookDownloadService {
|
||||
}
|
||||
|
||||
if (convertCbxToEpub) {
|
||||
fileToSend = cbxConversionService.convertCbxToEpub(inputFile, tempDir.toFile(), bookEntity);
|
||||
fileToSend = cbxConversionService.convertCbxToEpub(inputFile, tempDir.toFile(), bookEntity,compressionPercentage);
|
||||
}
|
||||
|
||||
if (convertEpubToKepub) {
|
||||
|
||||
@@ -102,20 +102,20 @@ public class CbxConversionService {
|
||||
* @throws IllegalArgumentException if the file format is not supported
|
||||
* @throws IllegalStateException if no valid images are found in the archive
|
||||
*/
|
||||
public File convertCbxToEpub(File cbxFile, File tempDir, BookEntity bookEntity)
|
||||
public File convertCbxToEpub(File cbxFile, File tempDir, BookEntity bookEntity, int compressionPercentage)
|
||||
throws IOException, TemplateException, RarException {
|
||||
validateInputs(cbxFile, tempDir);
|
||||
|
||||
log.info("Starting CBX to EPUB conversion for: {}", cbxFile.getName());
|
||||
|
||||
File outputFile = executeCbxConversion(cbxFile, tempDir, bookEntity);
|
||||
File outputFile = executeCbxConversion(cbxFile, tempDir, bookEntity,compressionPercentage);
|
||||
|
||||
log.info("Successfully converted {} to {} (size: {} bytes)",
|
||||
cbxFile.getName(), outputFile.getName(), outputFile.length());
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
private File executeCbxConversion(File cbxFile, File tempDir, BookEntity bookEntity)
|
||||
private File executeCbxConversion(File cbxFile, File tempDir, BookEntity bookEntity,int compressionPercentage)
|
||||
throws IOException, TemplateException, RarException {
|
||||
|
||||
Path epubFilePath = Paths.get(tempDir.getAbsolutePath(), cbxFile.getName() + ".epub");
|
||||
@@ -136,7 +136,7 @@ public class CbxConversionService {
|
||||
addMetaInfContainer(zipOut);
|
||||
addStylesheet(zipOut);
|
||||
|
||||
List<EpubContentFileGroup> contentGroups = addImagesAndPages(zipOut, imagePaths);
|
||||
List<EpubContentFileGroup> contentGroups = addImagesAndPages(zipOut, imagePaths,compressionPercentage);
|
||||
|
||||
addContentOpf(zipOut, bookEntity, contentGroups);
|
||||
addTocNcx(zipOut, bookEntity, contentGroups);
|
||||
@@ -340,13 +340,13 @@ public class CbxConversionService {
|
||||
zipOut.closeArchiveEntry();
|
||||
}
|
||||
|
||||
private List<EpubContentFileGroup> addImagesAndPages(ZipArchiveOutputStream zipOut, List<Path> imagePaths)
|
||||
private List<EpubContentFileGroup> addImagesAndPages(ZipArchiveOutputStream zipOut, List<Path> imagePaths,int compressionPercentage)
|
||||
throws IOException, TemplateException {
|
||||
|
||||
List<EpubContentFileGroup> contentGroups = new ArrayList<>();
|
||||
|
||||
if (!imagePaths.isEmpty()) {
|
||||
addImageToZipFromPath(zipOut, COVER_IMAGE_PATH, imagePaths.getFirst());
|
||||
addImageToZipFromPath(zipOut, COVER_IMAGE_PATH, imagePaths.getFirst(),compressionPercentage);
|
||||
}
|
||||
|
||||
for (int i = 0; i < imagePaths.size(); i++) {
|
||||
@@ -358,7 +358,7 @@ public class CbxConversionService {
|
||||
String imagePath = IMAGE_ROOT_PATH + imageFileName;
|
||||
String htmlPath = HTML_ROOT_PATH + htmlFileName;
|
||||
|
||||
addImageToZipFromPath(zipOut, imagePath, imageSourcePath);
|
||||
addImageToZipFromPath(zipOut, imagePath, imageSourcePath,compressionPercentage);
|
||||
|
||||
String htmlContent = generatePageHtml(imageFileName, i + 1);
|
||||
ZipArchiveEntry htmlEntry = new ZipArchiveEntry(htmlPath);
|
||||
@@ -372,7 +372,7 @@ public class CbxConversionService {
|
||||
return contentGroups;
|
||||
}
|
||||
|
||||
private void addImageToZipFromPath(ZipArchiveOutputStream zipOut, String epubImagePath, Path sourceImagePath)
|
||||
private void addImageToZipFromPath(ZipArchiveOutputStream zipOut, String epubImagePath, Path sourceImagePath,int compressionPercentage)
|
||||
throws IOException {
|
||||
ZipArchiveEntry imageEntry = new ZipArchiveEntry(epubImagePath);
|
||||
zipOut.putArchiveEntry(imageEntry);
|
||||
@@ -385,7 +385,7 @@ public class CbxConversionService {
|
||||
try (InputStream fis = Files.newInputStream(sourceImagePath)) {
|
||||
BufferedImage image = ImageIO.read(fis);
|
||||
if (image != null) {
|
||||
writeJpegImage(image, zipOut, 0.85f);
|
||||
writeJpegImage(image, zipOut, compressionPercentage/100f);
|
||||
} else {
|
||||
log.warn("Could not decode image {}, copying raw bytes", sourceImagePath.getFileName());
|
||||
try (InputStream rawStream = Files.newInputStream(sourceImagePath)) {
|
||||
|
||||
@@ -42,7 +42,7 @@ class CbxConversionIntegrationTest {
|
||||
File testCbzFile = createTestComicCbzFile();
|
||||
BookEntity bookMetadata = createTestBookMetadata();
|
||||
|
||||
File epubFile = conversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), bookMetadata);
|
||||
File epubFile = conversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), bookMetadata,85);
|
||||
|
||||
assertThat(epubFile)
|
||||
.exists()
|
||||
|
||||
@@ -42,7 +42,7 @@ class CbxConversionServiceTest {
|
||||
|
||||
@Test
|
||||
void convertCbxToEpub_WithValidCbzFile_ShouldGenerateValidEpub() throws IOException, TemplateException, RarException {
|
||||
File epubFile = cbxConversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), testBookEntity);
|
||||
File epubFile = cbxConversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), testBookEntity,85);
|
||||
|
||||
assertThat(epubFile).exists();
|
||||
assertThat(epubFile.getName()).endsWith(".epub");
|
||||
@@ -53,7 +53,7 @@ class CbxConversionServiceTest {
|
||||
|
||||
@Test
|
||||
void convertCbxToEpub_WithNullCbxFile_ShouldThrowException() {
|
||||
assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(null, tempDir.toFile(), testBookEntity))
|
||||
assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(null, tempDir.toFile(), testBookEntity,85))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Invalid CBX file");
|
||||
}
|
||||
@@ -62,7 +62,7 @@ class CbxConversionServiceTest {
|
||||
void convertCbxToEpub_WithNonExistentFile_ShouldThrowException() {
|
||||
File nonExistentFile = new File(tempDir.toFile(), "non-existent.cbz");
|
||||
|
||||
assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(nonExistentFile, tempDir.toFile(), testBookEntity))
|
||||
assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(nonExistentFile, tempDir.toFile(), testBookEntity,85))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Invalid CBX file");
|
||||
}
|
||||
@@ -71,14 +71,14 @@ class CbxConversionServiceTest {
|
||||
void convertCbxToEpub_WithUnsupportedFileFormat_ShouldThrowException() throws IOException {
|
||||
File unsupportedFile = Files.createFile(tempDir.resolve("test.txt")).toFile();
|
||||
|
||||
assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(unsupportedFile, tempDir.toFile(), testBookEntity))
|
||||
assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(unsupportedFile, tempDir.toFile(), testBookEntity,85))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Unsupported file format");
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertCbxToEpub_WithNullTempDir_ShouldThrowException() {
|
||||
assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(testCbzFile, null, testBookEntity))
|
||||
assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(testCbzFile, null, testBookEntity,85))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Invalid temp directory");
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class CbxConversionServiceTest {
|
||||
void convertCbxToEpub_WithEmptyCbzFile_ShouldThrowException() throws IOException {
|
||||
File emptyCbzFile = createEmptyCbzFile();
|
||||
|
||||
assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(emptyCbzFile, tempDir.toFile(), testBookEntity))
|
||||
assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(emptyCbzFile, tempDir.toFile(), testBookEntity,85))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessageContaining("No valid images found");
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class CbxConversionServiceTest {
|
||||
|
||||
@Test
|
||||
void convertCbxToEpub_WithNullBookEntity_ShouldUseDefaultMetadata() throws IOException, TemplateException, RarException {
|
||||
File epubFile = cbxConversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), null);
|
||||
File epubFile = cbxConversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), null,85);
|
||||
|
||||
assertThat(epubFile).exists();
|
||||
verifyEpubStructure(epubFile);
|
||||
@@ -128,7 +128,7 @@ class CbxConversionServiceTest {
|
||||
void convertCbxToEpub_WithMultipleImages_ShouldPreservePageOrder() throws IOException, TemplateException, RarException {
|
||||
File multiPageCbzFile = createMultiPageCbzFile();
|
||||
|
||||
File epubFile = cbxConversionService.convertCbxToEpub(multiPageCbzFile, tempDir.toFile(), testBookEntity);
|
||||
File epubFile = cbxConversionService.convertCbxToEpub(multiPageCbzFile, tempDir.toFile(), testBookEntity,85);
|
||||
|
||||
assertThat(epubFile).exists();
|
||||
verifyPageOrderInEpub(epubFile, 5);
|
||||
|
||||
@@ -263,7 +263,26 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label-row">
|
||||
<label class="setting-label">Conversion image compression: {{ koboSettings.conversionImageCompressionPercentage }}%</label>
|
||||
<div class="slider-container">
|
||||
<p-slider
|
||||
id="conversionLimit"
|
||||
[(ngModel)]="koboSettings.conversionImageCompressionPercentage"
|
||||
[min]="1"
|
||||
[max]="100"
|
||||
[step]="1"
|
||||
(ngModelChange)="onSliderChange()">
|
||||
</p-slider>
|
||||
</div>
|
||||
</div>
|
||||
<p class="setting-description">
|
||||
Comic book conversions can sometimes result in very large files. This setting allows you to compress the images during conversion to prevent size from shooting up.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label-row">
|
||||
|
||||
@@ -48,6 +48,7 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
|
||||
convertToKepub: false,
|
||||
conversionLimitInMb: 100,
|
||||
convertCbxToEpub: false,
|
||||
conversionImageCompressionPercentage: 85,
|
||||
conversionLimitInMbForCbx: 100,
|
||||
forceEnableHyphenation: false
|
||||
};
|
||||
@@ -138,6 +139,7 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy {
|
||||
this.koboSettings.convertCbxToEpub = settings?.koboSettings?.convertCbxToEpub ?? false;
|
||||
this.koboSettings.conversionLimitInMbForCbx = settings?.koboSettings?.conversionLimitInMbForCbx ?? 100;
|
||||
this.koboSettings.forceEnableHyphenation = settings?.koboSettings?.forceEnableHyphenation ?? false;
|
||||
this.koboSettings.conversionImageCompressionPercentage = settings?.koboSettings?.conversionImageCompressionPercentage ?? 85;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ export interface PublicReviewSettings {
|
||||
export interface KoboSettings {
|
||||
convertToKepub: boolean;
|
||||
conversionLimitInMb: number;
|
||||
conversionImageCompressionPercentage: number;
|
||||
convertCbxToEpub: boolean;
|
||||
conversionLimitInMbForCbx: number;
|
||||
forceEnableHyphenation: boolean;
|
||||
|
||||
Reference in New Issue
Block a user