1
0
mirror of https://github.com/ciromattia/kcc synced 2026-05-13 11:01:40 +00:00

Compare commits

..

30 Commits

Author SHA1 Message Date
Alex Xu
401876da22 Merge pull request #1338 from axu2/temp
Temporary File improvements
2026-05-11 16:40:46 -07:00
Alex Xu
ffeaaeca19 clean up temp files better 2026-05-11 16:34:11 -07:00
Alex Xu
b95bb12393 honor temp directory option in all locations 2026-05-11 11:53:20 -07:00
フィルターペーパー
4a6e4622ed Use tempdir option for fusion path
* Update makeFusion to use the same temporary directory location
* Avoid creating an orphan "KCC-" in TMPDIR when --tempdir is set
2026-05-11 10:33:50 -07:00
dependabot[bot]
a491810810 Bump signpath/github-action-submit-signing-request from 2.0 to 2.2 (#1337)
Bumps [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) from 2.0 to 2.2.
- [Release notes](https://github.com/signpath/github-action-submit-signing-request/releases)
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/v2.0...v2.2)

---
updated-dependencies:
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '2.2'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 19:00:05 -07:00
dependabot[bot]
75d0342fe1 Bump docker/build-push-action from 6 to 7 (#1336)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 18:59:54 -07:00
dependabot[bot]
60d41b25e4 Bump softprops/action-gh-release from 2 to 3 (#1335)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-10 18:59:42 -07:00
dependabot[bot]
0db788589d Bump docker/setup-qemu-action from 3 to 4 (#1263)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-08 12:05:27 -07:00
dependabot[bot]
f42e6aea5c Bump docker/login-action from 3 to 4 (#1262)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-08 12:05:16 -07:00
dependabot[bot]
53ae057cbb Bump docker/metadata-action from 5 to 6 (#1261)
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-08 12:05:05 -07:00
dependabot[bot]
d729839976 Bump docker/setup-buildx-action from 3 to 4 (#1260)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-08 12:04:51 -07:00
dependabot[bot]
42a50ed670 Bump actions/upload-artifact from 6 to 7 (#1256)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-08 12:04:38 -07:00
Alex Xu
e6ef7c1732 Bump actions/upload-artifact from 5 to 6 (#1332)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-08 11:59:01 -07:00
Alex Xu
f2a806a42a bumpy to 10.1.3 2026-05-06 08:39:27 -07:00
Alex Xu
8798d71bfa smart cover crop is default off (#1331) 2026-05-06 08:12:19 -07:00
Alex Xu
19ce14eeee KFX default upscale (#1329) 2026-05-04 16:40:40 -07:00
Alex Xu
5a1e2dafcb fix thin horizontal line in landscape in certain situations (#1326) 2026-05-04 15:22:37 -07:00
Alex Xu
f149ae23f3 add Kindle Scribe 2025 landscape 1324x1986 profile (#1325) 2026-05-03 16:48:05 -07:00
Alex Xu
2878e5d41b bump to 10.1.2 2026-05-02 15:17:36 -07:00
Alex Xu
bd691989a9 fix KFX on Windows 7 (#1323) 2026-05-02 15:13:21 -07:00
Alex Xu
d4aeb798c7 bump to 10.1.1 2026-05-02 14:43:12 -07:00
Alex Xu
997a514e2a fix list index out of range for kfx (#1321) 2026-05-02 14:42:46 -07:00
Alex Xu
a3672f7a1e bump to 10.1.0 2026-04-26 14:54:09 -07:00
Alex Xu
1b48a9fc5e Replace KFX (does not work) with KFX (Send to Kindle EPUB) (#1309)
* add better kfx output

* fix kfx res

* fix bug

* fix bug

* no errors

* kfx defaults to PNG

* KFX 200 MB default

* clarify send to kindle

* fit close images

* adjust

* refactor contain

* initial fixes
2026-04-26 14:53:28 -07:00
Alex Xu
d5dde46989 CBZ defaults to partially checked w/b borders (#1310) 2026-04-24 16:40:44 -07:00
Alex Xu
92c85c18e9 rename autocontrast box to custom autocontrast (#1308) 2026-04-24 12:11:38 -07:00
Alex Xu
e5122cc188 prevent creating non Kindle MOBI (#1307) 2026-04-24 07:44:56 -07:00
Alex Xu
61be6aa78e bump to 10.0.1 2026-04-23 14:56:04 -07:00
フィルターペーパー
d6834063c1 avoid orphan dir in tempdir and fix disk size check with tempdir (#1302)
* Use tempdir option for fusion path

* Update makeFusion to use the same temporary directory location
* Avoid creating an orphan "KCC-" in TMPDIR when --tempdir is set

* Ensure disk space check follows --tempdir setting

* revert some things

* revert some things
2026-04-23 14:53:45 -07:00
Alex Xu
1a8d74de4a fix webtoon smartcover (#1305) 2026-04-23 13:36:49 -07:00
14 changed files with 174 additions and 72 deletions

View File

@@ -24,17 +24,17 @@ jobs:
uses: actions/checkout@v6
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Set Release Date
id: release_date
@@ -43,7 +43,7 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository_owner }}/kcc
# Always creates the "latest" tag
@@ -54,7 +54,7 @@ jobs:
type=raw,value=${{ steps.release_date.outputs.release_date }}
- name: Build and push
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
context: .

View File

@@ -59,12 +59,12 @@ jobs:
env:
UPDATE_INFO: gh-releases-zsync|ciromattia|kcc|latest|*x86_64.AppImage.zsync
- name: upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: AppImage
path: './*.AppImage*'
- name: Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true

View File

@@ -80,12 +80,12 @@ jobs:
run: |
python setup.py build_binary
- name: upload build
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: mac-os-build-${{ runner.arch }}
path: dist/*.dmg
- name: Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true

View File

@@ -51,12 +51,12 @@ jobs:
run: |
python3 setup.py build_binary
- name: upload build
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: osx-build-${{ runner.arch }}
path: dist/*.dmg
- name: Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true

View File

@@ -53,12 +53,12 @@ jobs:
python setup.py ${{ matrix.command }}
- name: upload-unsigned-artifact
id: upload-unsigned-artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: windows-build-${{ matrix.entry }}
path: dist/*.exe
- id: optional_step_id
uses: signpath/github-action-submit-signing-request@v2.0
uses: signpath/github-action-submit-signing-request@v2.2
if: ${{ github.repository == 'ciromattia/kcc' }}
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
@@ -69,7 +69,7 @@ jobs:
wait-for-completion: true
output-artifact-directory: 'dist/'
- name: Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true

View File

@@ -46,12 +46,12 @@ jobs:
python setup.py build_binary
- name: upload-unsigned-artifact
id: upload-unsigned-artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: windows7-build
path: dist/*.exe
- id: optional_step_id
uses: signpath/github-action-submit-signing-request@v2.0
uses: signpath/github-action-submit-signing-request@v2.2
if: ${{ github.repository == 'ciromattia/kcc' }}
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
@@ -62,7 +62,7 @@ jobs:
wait-for-completion: true
output-artifact-directory: 'dist/'
- name: Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true

View File

@@ -199,6 +199,8 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi
'KCS': ("Kindle Colorsoft", (1272, 1696), Palette16, 1.0),
'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0),
'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0),
'KS1240': ("Kindle 1240", (1240, 1860), Palette16, 1.0),
'KS1324': ("Kindle 1324", (1324, 1986), Palette16, 1.0),
'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0),
'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0),
'KSCS': ("Kindle Scribe Colorsoft", (1986, 2648), Palette16, 1.0),
@@ -267,7 +269,7 @@ PROCESSING:
Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0]
--blackborders Disable autodetection and force black borders
--whiteborders Disable autodetection and force white borders
--nosmartcovercrop Disable attempt to crop main cover from wide image
--smartcovercrop Attempt to crop main cover from wide image
--coverfill Center-crop only the cover to fill target device screen
--forcecolor Don't convert images to grayscale
--forcepng Create PNG files instead JPEG for black and white images

View File

@@ -655,12 +655,12 @@ Higher values are larger and higher quality, and may resolve blank page issues.<
</widget>
</item>
<item row="11" column="1">
<widget class="QCheckBox" name="noSmartCoverCropBox">
<widget class="QCheckBox" name="smartCoverCropBox">
<property name="toolTip">
<string>Disable attempt to crop main cover from wide image.</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Attempt to crop main cover from wide image.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>No Smart Cover Crop</string>
<string>Smart Cover Crop</string>
</property>
</widget>
</item>
@@ -866,7 +866,7 @@ Useful if you plan to crop a little off the top and bottom to fill screen.</stri
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - BW only&lt;br/&gt;&lt;/span&gt;Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - Disabled&lt;br/&gt;&lt;/span&gt;Disable autocontrast&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - BW and Color&lt;br/&gt;&lt;/span&gt;BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Autocontrast</string>
<string>Custom Autocontrast</string>
</property>
<property name="tristate">
<bool>true</bool>

View File

@@ -331,8 +331,8 @@ class WorkerThread(QThread):
options.pdfextract = True
if GUI.pdfWidthBox.isChecked():
options.pdfwidth = True
if GUI.noSmartCoverCropBox.isChecked():
options.nosmartcovercrop = True
if GUI.smartCoverCropBox.isChecked():
options.smartcovercrop = True
if GUI.coverFillBox.isChecked():
options.coverfill = True
if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked:
@@ -564,6 +564,7 @@ class WorkerThread(QThread):
os.remove(path)
elif os.path.isdir(path):
rmtree(path, True)
comic2ebook.checkPre('LLL-')
GUI.progress.content = ''
GUI.progress.stop()
MW.hideProgressBar.emit()
@@ -946,10 +947,16 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'MOBI+EPUB-200MB'):
GUI.chunkSizeCheckBox.setEnabled(False)
GUI.chunkSizeCheckBox.setChecked(False)
elif GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'KFX':
GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked)
GUI.upscaleBox.setChecked(True)
elif not GUI.webtoonBox.isChecked():
GUI.chunkSizeCheckBox.setEnabled(True)
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'PDF') and not GUI.webtoonBox.isChecked():
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
else:
GUI.borderBox.setCheckState(Qt.CheckState.Unchecked)
def stripTags(self, html):
s = HTMLStripper()
@@ -1079,7 +1086,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'disableProcessingBox': GUI.disableProcessingBox.checkState(),
'pdfExtractBox': GUI.pdfExtractBox.checkState(),
'pdfWidthBox': GUI.pdfWidthBox.checkState(),
'noSmartCoverCropBox': GUI.noSmartCoverCropBox.checkState(),
'smartCoverCropBox': GUI.smartCoverCropBox.checkState(),
'coverFillBox': GUI.coverFillBox.checkState(),
'metadataTitleBox': GUI.metadataTitleBox.checkState(),
'mozJpegBox': GUI.mozJpegBox.checkState(),
@@ -1231,7 +1238,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
"PDF (200MB limit)": {'icon': 'EPUB', 'format': 'PDF-200MB'},
"KFX (does not work)": {'icon': 'KFX', 'format': 'KFX'},
"KFX (Send to Kindle EPUB)": {'icon': 'KFX', 'format': 'KFX'},
"MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'},
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'},
"MOBI + EPUB (200MB limit)": {'icon': 'MOBI', 'format': 'MOBI+EPUB-200MB'},
@@ -1256,6 +1263,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"Kindle 1240x1860": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1240',
},
"Kindle 1324x1986": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1324',
},
"Kindle Scribe 1/2": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS',
},
@@ -1363,6 +1373,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"Separator",
"Other",
"Separator",
"Kindle 1324x1986",
"Kindle 1920x1920",
"Kindle 1860x1920",
"Kindle 1240x1860",

View File

@@ -344,10 +344,10 @@ class Ui_mainWindow(object):
self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1)
self.noSmartCoverCropBox = QCheckBox(self.optionWidget)
self.noSmartCoverCropBox.setObjectName(u"noSmartCoverCropBox")
self.smartCoverCropBox = QCheckBox(self.optionWidget)
self.smartCoverCropBox.setObjectName(u"smartCoverCropBox")
self.gridLayout_2.addWidget(self.noSmartCoverCropBox, 11, 1, 1, 1)
self.gridLayout_2.addWidget(self.smartCoverCropBox, 11, 1, 1, 1)
self.rotateFirstBox = QCheckBox(self.optionWidget)
self.rotateFirstBox.setObjectName(u"rotateFirstBox")
@@ -753,9 +753,9 @@ class Ui_mainWindow(object):
#endif // QT_CONFIG(tooltip)
self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None))
#if QT_CONFIG(tooltip)
self.noSmartCoverCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"Disable attempt to crop main cover from wide image.", None))
self.smartCoverCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Attempt to crop main cover from wide image.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.noSmartCoverCropBox.setText(QCoreApplication.translate("mainWindow", u"No Smart Cover Crop", None))
self.smartCoverCropBox.setText(QCoreApplication.translate("mainWindow", u"Smart Cover Crop", None))
#if QT_CONFIG(tooltip)
self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
@@ -819,7 +819,7 @@ class Ui_mainWindow(object):
#if QT_CONFIG(tooltip)
self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - BW only<br/></span>Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Disabled<br/></span>Disable autocontrast</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - BW and Color<br/></span>BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None))
self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Custom Autocontrast", None))
#if QT_CONFIG(tooltip)
self.webpBox.setToolTip(QCoreApplication.translate("mainWindow", u"Replace JPG with lossy WebP and PNG with lossless WebP. This includes the JPG Quality.\n"
"\n"

View File

@@ -1,4 +1,4 @@
__version__ = '10.0.0'
__version__ = '10.1.3'
__license__ = 'ISC'
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
__docformat__ = 'restructuredtext en'

View File

@@ -18,6 +18,7 @@
# PERFORMANCE OF THIS SOFTWARE.
#
from collections import Counter
import os
import pathlib
import re
@@ -29,8 +30,8 @@ from glob import glob, escape
from re import sub
from stat import S_IWRITE, S_IREAD, S_IEXEC
from typing import List
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from tempfile import mkdtemp, gettempdir, TemporaryFile
from zipfile import ZipFile, ZIP_STORED
from tempfile import mkdtemp, gettempdir
from shutil import move, copytree, rmtree, copyfile
from multiprocessing import Pool, cpu_count
from uuid import uuid4
@@ -43,7 +44,7 @@ from psutil import virtual_memory, disk_usage
from html import escape as hescape
import pymupdf
from .shared import IMAGE_TYPES, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean
from .shared import IMAGE_TYPES, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean, get_contain_resolution
from .comicarchive import SEVENZIP, available_archive_tools
from . import comic2panel
from . import image
@@ -89,6 +90,7 @@ def main(argv=None):
os.remove(path)
elif os.path.isdir(path):
rmtree(path, True)
checkPre('LLL-')
return 0
@@ -143,7 +145,7 @@ def buildHTML(path, imgfile, imgfilepath, imgfile2=None):
f.write('<div style="display:none;">.</div>\n')
f.write(f'<img width="{imgsize[0]}" height="{imgsize[1]}" src="{"../" * backref}Images/{postfix}{imgfile}"/>\n')
if imgfile2:
f.write(f'<img style="bottom: 0" width="{imgsize2[0]}" height="{imgsize2[1]}" src="{"../" * backref}Images/{postfix}{imgfile2}"/>\n')
f.write(f'<img style="top: 1920px" width="{imgsize2[0]}" height="{imgsize2[1]}" src="{"../" * backref}Images/{postfix}{imgfile2}"/>\n')
f.write("</div>\n")
if options.iskindle and options.panelview:
if options.autoscale:
@@ -324,8 +326,19 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
f.write("<meta name=\"cover\" content=\"cover\"/>\n")
if options.iskindle and options.profile != 'Custom':
f.writelines(["<meta name=\"fixed-layout\" content=\"true\"/>\n",
"<meta name=\"original-resolution\" content=\"",
str(deviceres[0]) + "x" + str(deviceres[1]) + "\"/>\n",
])
if not options.kfx_resolution:
f.writelines([
"<meta name=\"original-resolution\" content=\"",
str(deviceres[0]) + "x" + str(deviceres[1]) + "\"/>\n",
])
else:
x, y = options.kfx_resolution
f.writelines([
"<meta name=\"original-resolution\" content=\"",
str(x) + "x" + str(y) + "\"/>\n",
])
f.writelines([
"<meta name=\"book-type\" content=\"comic\"/>\n",
"<meta name=\"primary-writing-mode\" content=\"" + writingmode + "\"/>\n",
"<meta name=\"zero-gutter\" content=\"true\"/>\n",
@@ -873,14 +886,21 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_
def getWorkFolder(afile, workdir=None):
if not workdir:
workdir = mkdtemp('', 'KCC-')
if options.tempdir:
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
else:
workdir = mkdtemp('', 'KCC-')
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
else:
fullPath = workdir
if options.tempdir:
check_path = os.path.dirname(afile)
else:
check_path = gettempdir()
if os.path.isdir(afile):
if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
if disk_usage(check_path)[2] < getDirectorySize(afile) * 2.5:
raise UserWarning("Not enough disk space to perform conversion.")
try:
copytree(afile, fullPath)
@@ -890,7 +910,7 @@ def getWorkFolder(afile, workdir=None):
rmtree(workdir, True)
raise UserWarning("Failed to prepare a workspace.")
elif os.path.isfile(afile):
if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5:
if disk_usage(check_path)[2]< os.path.getsize(afile) * 2.5:
raise UserWarning("Not enough disk space to perform conversion.")
if afile.lower().endswith('.pdf'):
if not os.path.exists(fullPath):
@@ -1390,8 +1410,8 @@ def makeParser():
help="Use the legacy PDF image extraction method from KCC 8 and earlier")
processing_options.add_argument("--pdfwidth", action="store_true", dest="pdfwidth", default=False,
help="Render vector PDFs to device width instead of height.")
processing_options.add_argument("--nosmartcovercrop", action="store_true", dest="nosmartcovercrop", default=False,
help="Disable attempt to crop main cover from wide image")
processing_options.add_argument("--smartcovercrop", action="store_true", dest="smartcovercrop", default=False,
help="Attempt to crop main cover from wide image")
processing_options.add_argument("--coverfill", action="store_true", dest="coverfill", default=False,
help="Crop cover to fill screen")
processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False,
@@ -1466,6 +1486,15 @@ def checkOptions(options):
options.isKobo = False
options.bordersColor = None
options.keep_epub = False
if options.profile in image.ProfileData.ProfilesKindle.keys():
options.iskindle = True
else:
options.isKobo = True
if not options.iskindle and ('MOBI' in options.format or 'EPUB-200MB' in options.format or 'KFX' in options.format):
raise UserWarning('MOBI/Send to Kindle not supported for non-Kindle profiles')
if options.format == 'PDF-200MB':
options.targetsize = 195
options.format = 'PDF'
@@ -1497,10 +1526,7 @@ def checkOptions(options):
options.format = 'PDF'
else:
options.format = 'EPUB'
if options.profile in image.ProfileData.ProfilesKindle.keys():
options.iskindle = True
else:
options.isKobo = True
if options.white_borders:
options.bordersColor = 'white'
if options.black_borders:
@@ -1531,6 +1557,7 @@ def checkOptions(options):
options.hq = False
# KFX output create EPUB that might be can be by jhowell KFX Output Calibre plugin
if options.format == 'KFX':
options.targetsize = 195
options.format = 'EPUB'
options.kfx = True
options.panelview = False
@@ -1552,6 +1579,7 @@ def checkOptions(options):
options.jpegquality = 90
else:
options.jpegquality = 85
options.kindle_azw3 = options.iskindle and ('MOBI' in options.format or 'EPUB' in options.format)
options.kindle_scribe_azw3 = options.profile.startswith('KS') and options.kindle_azw3
@@ -1585,33 +1613,30 @@ def checkTools(source):
sys.exit(1)
def checkPre(source):
def checkPre(source='KCC-'):
# Make sure that all temporary files are gone
for root, dirs, _ in walkLevel(gettempdir(), 0):
for tempdir in dirs:
if tempdir.startswith('KCC-'):
if tempdir.startswith(source):
rmtree(os.path.join(root, tempdir), True)
# Make sure that target directory is writable
if os.path.isdir(source):
src = os.path.abspath(os.path.join(source, '..'))
else:
src = os.path.dirname(source)
try:
with TemporaryFile(prefix='KCC-', dir=src):
pass
except Exception:
raise UserWarning("Target directory is not writable.")
def makeFusion(sources: List[str]):
if len(sources) < 2:
raise UserWarning('Fusion requires at least 2 sources. Did you forget to uncheck fusion?')
start = perf_counter()
first_path = Path(sources[0])
if first_path.is_file():
fusion_path = first_path.parent.joinpath(first_path.stem + ' [fused]')
if options.tempdir:
fusion_parent = first_path.parent
else:
fusion_path = first_path.parent.joinpath(first_path.name + ' [fused]')
# LLL is after KCC
checkPre('LLL-')
fusion_parent = Path(mkdtemp('', 'LLL-'))
if first_path.is_file():
fusion_path = fusion_parent.joinpath(first_path.stem + ' [fused]')
else:
fusion_path = fusion_parent.joinpath(first_path.name + ' [fused]')
print("Running Fusion")
# Check if prefix is needed when user-specified ordering differs from OS natural sorting
@@ -1620,7 +1645,6 @@ def makeFusion(sources: List[str]):
for index, source in enumerate(sources, start=1):
print(f"Processing {source}...")
checkPre(source)
print("Checking images...")
source_path = Path(source)
# Add the fusion_0001_ prefix to maintain user-specified order if needed
@@ -1652,7 +1676,9 @@ def makeBook(source, qtgui=None, job_progress=''):
GUI.progressBarTick.emit('1')
else:
checkTools(source)
checkPre(source)
checkPre()
if not options.filefusion:
checkPre('LLL-')
print(f"{job_progress}Preparing source images...")
path = getWorkFolder(source)
print(f"{job_progress}Checking images...")
@@ -1667,9 +1693,43 @@ def makeBook(source, qtgui=None, job_progress=''):
if not options.webtoon:
cover = image.Cover(cover_path, options)
x, y = image.ProfileData.Profiles[options.profile][1]
if options.webtoon:
x, y = image.ProfileData.Profiles[options.profile][1]
comic2panel.main(['-y ' + str(y), '-x' + str(x), '-i', '-m', path], job_progress, qtgui)
options.kfx_resolution = None
if options.kfx:
original_resolutions = []
normalized_resolutions = []
for root, _, files in os.walk(os.path.join(path, "OEBPS", "Images")):
for file in files:
with Image.open(os.path.join(root, file)) as imagef:
original_resolutions.append(imagef.size)
size = get_contain_resolution(imagef, (x, y))
normalized_resolutions.append(size)
counter = Counter(normalized_resolutions)
aspect_ratios = []
filtered_resolutions = []
for w, h in normalized_resolutions:
aspect_ratio = h / w
# page-like aspect ratios, could be improved
if aspect_ratio > 1.3 and aspect_ratio < 1.7:
aspect_ratios.append(aspect_ratio)
filtered_resolutions.append((w, h))
most_common_res, most_common_count = counter.most_common(1)[0]
options.kfx_resolution = most_common_res
if most_common_count / sum(counter.values()) > .6:
pass
#elif max(aspect_ratios) - min(aspect_ratios) < .2:
else:
# get the widest resolution
options.kfx_resolution = max(filtered_resolutions)
# else:
# raise UserWarning('Aspect ratio of pages too different for KFX conversion')
if options.noprocessing:
print(f"{job_progress}Do not process image, ignore any profile or processing option")
else:
@@ -1710,7 +1770,7 @@ def makeBook(source, qtgui=None, job_progress=''):
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
else:
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
if cover.smartcover:
if cover and cover.smartcover:
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress)
elif options.format == 'PDF':
@@ -1718,7 +1778,7 @@ def makeBook(source, qtgui=None, job_progress=''):
# determine output filename based on source and tome count
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
if cover.smartcover:
if cover and cover.smartcover:
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
# use optimized buildPDF logic with streaming and compression
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file)

View File

@@ -29,6 +29,7 @@ from PIL import Image, ImageOps, ImageFile, ImageChops, ImageDraw
from .rainbow_artifacts_eraser import erase_rainbow_artifacts
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
from .inter_panel_crop_alg import crop_empty_inter_panel
from .shared import get_contain_resolution
AUTO_CROP_THRESHOLD = 0.015
ImageFile.LOAD_TRUNCATED_IMAGES = True
@@ -105,6 +106,7 @@ class ProfileData:
'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0),
'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0),
'KS1240': ("Kindle 1240", (1240, 1860), Palette16, 1.0),
'KS1324': ("Kindle 1324", (1324, 1986), Palette16, 1.0),
'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0),
'KCS': ("Kindle Colorsoft", (1272, 1696), Palette16, 1.0),
'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0),
@@ -517,7 +519,17 @@ class ComicPage:
ratio_device = float(self.size[1]) / float(self.size[0])
ratio_image = float(self.image.size[1]) / float(self.image.size[0])
method = self.resize_method()
if self.opt.stretch:
if self.opt.kfx:
ratio_kfx = self.opt.kfx_resolution[1] / self.opt.kfx_resolution[0]
contain_size = get_contain_resolution(self.image, self.size)
if abs(ratio_image - ratio_kfx) < AUTO_CROP_THRESHOLD:
if contain_size[0] > self.opt.kfx_resolution[0] or contain_size[1] > self.opt.kfx_resolution[1]:
self.image = ImageOps.fit(self.image, self.opt.kfx_resolution, method=method)
else:
self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill)
else:
self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill)
elif self.opt.stretch:
self.image = self.image.resize(self.size, method)
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
pass
@@ -526,7 +538,7 @@ class ComicPage:
self.image = ImageOps.fit(self.image, self.size, method=method)
elif abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
self.image = ImageOps.fit(self.image, self.size, method=method)
elif (self.opt.format in ('CBZ', 'PDF') or self.opt.kfx) and not self.opt.white_borders:
elif (self.opt.format in ('CBZ', 'PDF')) and not self.opt.white_borders:
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
else:
self.image = ImageOps.contain(self.image, self.size, method=method)
@@ -587,7 +599,7 @@ class Cover:
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
if not self.options.forcecolor:
self.image = self.image.convert('L')
if not self.options.nosmartcovercrop:
if self.options.smartcovercrop:
self.crop_main_cover()
size = list(self.options.profileData[1])

View File

@@ -61,6 +61,23 @@ def getImageFileName(imgfile):
ext = ext.lower()
return [name, ext]
def get_contain_resolution(image, size):
'''same code as Pillow ImageOps.contain()'''
im_ratio = image.width / image.height
dest_ratio = size[0] / size[1]
if im_ratio != dest_ratio:
if im_ratio > dest_ratio:
new_height = round(image.height / image.width * size[0])
if new_height != size[1]:
size = (size[0], new_height)
else:
new_width = round(image.width / image.height * size[1])
if new_width != size[0]:
size = (new_width, size[1])
return size
def walkSort(dirnames, filenames):
convert = lambda text: int(text) if text.isdigit() else text