mirror of
https://github.com/ciromattia/kcc
synced 2026-04-15 13:38:46 +00:00
Compare commits
48 Commits
axu2-patch
...
v7.3.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
271a129537 | ||
|
|
23099cee81 | ||
|
|
b957fcf3fe | ||
|
|
187475a424 | ||
|
|
88fd54e2ba | ||
|
|
b23b67bbbe | ||
|
|
9992d895cf | ||
|
|
561951a349 | ||
|
|
b8b7926366 | ||
|
|
92f3308e1c | ||
|
|
9e87ccef4e | ||
|
|
9a2a09eab9 | ||
|
|
88cf2fd21f | ||
|
|
7a3ed262b1 | ||
|
|
9e204aad76 | ||
|
|
e1f9d12676 | ||
|
|
24ab72fcbc | ||
|
|
4af6a75874 | ||
|
|
28b6188a3f | ||
|
|
f000af1207 | ||
|
|
299f916580 | ||
|
|
c39e403595 | ||
|
|
e787dd2897 | ||
|
|
01625904d1 | ||
|
|
5f8526da44 | ||
|
|
1159e737a0 | ||
|
|
5bbdb715e9 | ||
|
|
1a3cd6c916 | ||
|
|
e1e6d587f4 | ||
|
|
ca5c0bdd61 | ||
|
|
c6f491d27e | ||
|
|
c9ed3feef1 | ||
|
|
be147fe7e5 | ||
|
|
62ffa2bc80 | ||
|
|
11186d07c0 | ||
|
|
4b3cd6882a | ||
|
|
b35a2baf05 | ||
|
|
11a395e983 | ||
|
|
2e39a8c227 | ||
|
|
02535421a0 | ||
|
|
3d4fae62d8 | ||
|
|
2b550b8b98 | ||
|
|
ecee7cf6f5 | ||
|
|
b0a5558da1 | ||
|
|
1b487c18d6 | ||
|
|
a3546d19c3 | ||
|
|
2f703ef92c | ||
|
|
4fb993b38b |
@@ -40,11 +40,25 @@ jobs:
|
|||||||
version_built=$(cat kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/[^.0-9b]//g")
|
version_built=$(cat kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/[^.0-9b]//g")
|
||||||
mv dist/windows/kcc-c2e.exe dist/windows/KCC_c2e_${version_built}.exe
|
mv dist/windows/kcc-c2e.exe dist/windows/KCC_c2e_${version_built}.exe
|
||||||
mv dist/windows/kcc-c2p.exe dist/windows/KCC_c2p_${version_built}.exe
|
mv dist/windows/kcc-c2p.exe dist/windows/KCC_c2p_${version_built}.exe
|
||||||
- name: upload build
|
|
||||||
|
- name: upload-unsigned-artifact
|
||||||
|
id: upload-unsigned-artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-build
|
name: windows-build
|
||||||
path: dist/windows/*.exe
|
path: dist/windows/*.exe
|
||||||
|
|
||||||
|
- id: optional_step_id
|
||||||
|
uses: signpath/github-action-submit-signing-request@v1.1
|
||||||
|
with:
|
||||||
|
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||||
|
organization-id: '1dc1bad6-4a8c-4f85-af30-5c5d3d392ea6'
|
||||||
|
project-slug: 'kcc'
|
||||||
|
signing-policy-slug: 'release-signing'
|
||||||
|
github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}'
|
||||||
|
wait-for-completion: true
|
||||||
|
output-artifact-directory: 'dist/windows/signed/'
|
||||||
|
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
@@ -54,4 +68,4 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
LICENSE.txt
|
LICENSE.txt
|
||||||
dist/windows/*.exe
|
dist/windows/signed/*.exe
|
||||||
|
|||||||
15
.github/workflows/package-windows.yml
vendored
15
.github/workflows/package-windows.yml
vendored
@@ -41,11 +41,22 @@ jobs:
|
|||||||
- name: build binary
|
- name: build binary
|
||||||
run: |
|
run: |
|
||||||
python setup.py build_binary
|
python setup.py build_binary
|
||||||
- name: upload build
|
- name: upload-unsigned-artifact
|
||||||
|
id: upload-unsigned-artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-build
|
name: windows-build
|
||||||
path: dist/*.exe
|
path: dist/*.exe
|
||||||
|
- id: optional_step_id
|
||||||
|
uses: signpath/github-action-submit-signing-request@v1.1
|
||||||
|
with:
|
||||||
|
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||||
|
organization-id: '1dc1bad6-4a8c-4f85-af30-5c5d3d392ea6'
|
||||||
|
project-slug: 'kcc'
|
||||||
|
signing-policy-slug: 'release-signing'
|
||||||
|
github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}'
|
||||||
|
wait-for-completion: true
|
||||||
|
output-artifact-directory: 'dist/windows/signed/'
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
@@ -55,4 +66,4 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
LICENSE.txt
|
LICENSE.txt
|
||||||
dist/*.exe
|
dist/windows/signed/*.exe
|
||||||
|
|||||||
30
.travis.yml
30
.travis.yml
@@ -1,30 +0,0 @@
|
|||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: osx
|
|
||||||
language: generic
|
|
||||||
osx_image: xcode11.1
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- pip3 install --upgrade pip setuptools wheel
|
|
||||||
|
|
||||||
install:
|
|
||||||
- pip3 install -r requirements.txt
|
|
||||||
- pip3 install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip
|
|
||||||
- npm install -g appdmg
|
|
||||||
|
|
||||||
script: python3 setup.py build_binary
|
|
||||||
|
|
||||||
before_deploy:
|
|
||||||
- shopt -s extglob
|
|
||||||
- rm -r dist/!(*.deb|*.dmg)
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
provider: gcs
|
|
||||||
access_key_id: GOOG1EC62457RKUYFR2TIZUWV4EFSV2EP5LVLPPFXUAKADWJFDYPFW63BQSLA
|
|
||||||
secret_access_key:
|
|
||||||
secure: sxYjeho7U3im0Ezf6cz6TjYDiLvf0kAM2ETQHYoFNbD1VVvhJJyymDCnPH80zpFKmhc1MWTB6ndwsrPfcyZDLR2meSdWGPjZfFPY3RcrfImndKi7ln+mYQDBQ7W1lGit4YcH3Ju7LHceaTbRA7fVTX8pWKOcbXL2oM+lQxTJHH32+crVma+ChhbjzTWsSLRoakt3Nhiveec5p/qSW7AFe4Zq+b3C85IgwjSJI/xVwzaWrs6p915h1zZi7KL7YCMIxfQFrvRPFR2KTbh/DoLCCrqfbD4qh0PVy1li51Ac3hd/u3foiNnTNchzgE3Nv/nbKmtFU6huuLNgzkQGuLA+yn7mKYzBwA3ZmFgoimdH9+yRCMkZ8B5VHpvfN1hgpJcyEl1T98Kv4cdtRYNB4w9iAMy1qSVxhjeI+2rjuWGoXro0lU6L4LIRCOruY3AuLCAKG8Qw5Ak9ksmIKBhZ9soxpoIwu/TYDUQkFj29IrUQucg9TEp7uAoxu8/7EHxB7hWnBRaBAAQbMuIRg7yysT3FT0Os6SB0t9+RBsVMSPuIti9JJZ2Lu0uRI1+Se+g7ItzYtJoPhBJAzAa+J9OONj0RNj2z8Vq2oIBhH4z6b6zTRMVroos3cdfYl5qIKs9SQ7rmeHoPRROcqpCznsUZ/ESa4f2MewFU/7AYcEnCesZV4xg=
|
|
||||||
bucket: kcc-deploy
|
|
||||||
local-dir: dist
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
repo: AcidWeb/KCC
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
ISC LICENSE
|
ISC LICENSE
|
||||||
|
|
||||||
Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
Copyright (c) 2012-2025 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||||
Copyright (c) 2013-2019 Paweł Jastrzębski <pawelj@iosphe.re>
|
Copyright (c) 2013-2019 Paweł Jastrzębski <pawelj@iosphe.re>
|
||||||
Copyright (c) 2021-2023 Darodi
|
Copyright (c) 2021-2023 Darodi (https://github.com/darodi)
|
||||||
|
Copyright (c) 2023-2025 Alex Xu (https://github.com/axu2)
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for
|
Permission to use, copy, modify, and/or distribute this software for
|
||||||
any purpose with or without fee is hereby granted, provided that the
|
any purpose with or without fee is hereby granted, provided that the
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -11,6 +11,8 @@ It was initially developed for Kindle but since version 4.6 it outputs valid EPU
|
|||||||
actually a comic/manga to EPUB converter that every e-reader owner can happily use**_.
|
actually a comic/manga to EPUB converter that every e-reader owner can happily use**_.
|
||||||
It can also optionally optimize images by applying a number of transformations.
|
It can also optionally optimize images by applying a number of transformations.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### A word of warning
|
### A word of warning
|
||||||
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
||||||
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
||||||
@@ -31,6 +33,9 @@ If you find **KCC** valuable you can consider donating to the authors:
|
|||||||
- Alex Xu (active 2023-Present)
|
- Alex Xu (active 2023-Present)
|
||||||
- [](https://www.paypal.com/donate/?business=QFJVE7A6LCP6U&no_recurring=0&item_name=Kindle+Comic+Converter¤cy_code=USD)
|
- [](https://www.paypal.com/donate/?business=QFJVE7A6LCP6U&no_recurring=0&item_name=Kindle+Comic+Converter¤cy_code=USD)
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
- Free code signing on Windows provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
|
||||||
|
|
||||||
## DOWNLOADS
|
## DOWNLOADS
|
||||||
|
|
||||||
@@ -68,9 +73,11 @@ If you have issues detecting it, get stuck on the MOBI conversion step, or use L
|
|||||||
|
|
||||||
### 7-Zip
|
### 7-Zip
|
||||||
|
|
||||||
This is only required for certain files and advanced features.
|
This is optional but will make conversions much faster.
|
||||||
|
|
||||||
KCC will ask you to install if needed.
|
This is required for certain files and advanced features.
|
||||||
|
|
||||||
|
KCC will ask you to install if needed.
|
||||||
|
|
||||||
Refer to the wiki to install: https://github.com/ciromattia/kcc/wiki/Installation#7-zip
|
Refer to the wiki to install: https://github.com/ciromattia/kcc/wiki/Installation#7-zip
|
||||||
|
|
||||||
@@ -163,6 +170,8 @@ PROCESSING:
|
|||||||
Set cropping power [Default=1.0]
|
Set cropping power [Default=1.0]
|
||||||
--cm CROPPINGM, --croppingminimum CROPPINGM
|
--cm CROPPINGM, --croppingminimum CROPPINGM
|
||||||
Set cropping minimum area ratio [Default=0.0]
|
Set cropping minimum area ratio [Default=0.0]
|
||||||
|
--ipc INTERPANELCROP, --interpanelcrop INTERPANELCROP
|
||||||
|
Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0]
|
||||||
--blackborders Disable autodetection and force black borders
|
--blackborders Disable autodetection and force black borders
|
||||||
--whiteborders Disable autodetection and force white borders
|
--whiteborders Disable autodetection and force white borders
|
||||||
--forcecolor Don't convert images to grayscale
|
--forcecolor Don't convert images to grayscale
|
||||||
@@ -185,6 +194,7 @@ OUTPUT SETTINGS:
|
|||||||
Split output into multiple files. 0: Don't split 1: Automatic mode 2: Consider every subdirectory as separate volume [Default=0]
|
Split output into multiple files. 0: Don't split 1: Automatic mode 2: Consider every subdirectory as separate volume [Default=0]
|
||||||
--spreadshift Shift first page to opposite side in landscape for two page spread alignment
|
--spreadshift Shift first page to opposite side in landscape for two page spread alignment
|
||||||
--norotate Do not rotate double page spreads in spread splitter option.
|
--norotate Do not rotate double page spreads in spread splitter option.
|
||||||
|
--reducerainbow Reduce rainbow effect on color eink by slightly blurring images
|
||||||
|
|
||||||
CUSTOM PROFILE:
|
CUSTOM PROFILE:
|
||||||
--customwidth CUSTOMWIDTH
|
--customwidth CUSTOMWIDTH
|
||||||
@@ -229,6 +239,8 @@ If you want to edit the code, a good code editor is [VS Code](https://code.visua
|
|||||||
If you want to edit the `.ui` files, use [Qt Creator](https://www.qt.io/download-qt-installer-oss), included in **Qt for desktop development**.
|
If you want to edit the `.ui` files, use [Qt Creator](https://www.qt.io/download-qt-installer-oss), included in **Qt for desktop development**.
|
||||||
Then use the `gen_ui_files` scripts to autogenerate the python UI.
|
Then use the `gen_ui_files` scripts to autogenerate the python UI.
|
||||||
|
|
||||||
|
An example PR adding a new checkbox is here: https://github.com/ciromattia/kcc/pull/785
|
||||||
|
|
||||||
|
|
||||||
### Windows install from source
|
### Windows install from source
|
||||||
|
|
||||||
@@ -247,6 +259,12 @@ venv\Scripts\activate.bat
|
|||||||
python kcc.py
|
python kcc.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can build a `.exe` of KCC like the downloads we offer with
|
||||||
|
|
||||||
|
```
|
||||||
|
python setup.py build_binary
|
||||||
|
```
|
||||||
|
|
||||||
### macOS install from source
|
### macOS install from source
|
||||||
|
|
||||||
One time setup and running for the first time:
|
One time setup and running for the first time:
|
||||||
@@ -264,6 +282,12 @@ source venv/bin/activate
|
|||||||
python kcc.py
|
python kcc.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can build a `.app` of KCC like the downloads we offer with
|
||||||
|
|
||||||
|
```
|
||||||
|
python setup.py build_binary
|
||||||
|
```
|
||||||
|
|
||||||
## CREDITS
|
## CREDITS
|
||||||
**KCC** is made by
|
**KCC** is made by
|
||||||
|
|
||||||
@@ -300,5 +324,5 @@ The app relies and includes the following scripts:
|
|||||||
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||||
|
|
||||||
## COPYRIGHT
|
## COPYRIGHT
|
||||||
Copyright (c) 2012-2023 Ciro Mattia Gonano, Paweł Jastrzębski and Darodi.
|
Copyright (c) 2012-2025 Ciro Mattia Gonano, Paweł Jastrzębski, Darodi and Alex Xu.
|
||||||
**KCC** is released under ISC LICENSE; see [LICENSE.txt](./LICENSE.txt) for further details.
|
**KCC** is released under ISC LICENSE; see [LICENSE.txt](./LICENSE.txt) for further details.
|
||||||
|
|||||||
14
appveyor.yml
14
appveyor.yml
@@ -1,14 +0,0 @@
|
|||||||
environment:
|
|
||||||
PYTHON: "C:\\Python37-x64"
|
|
||||||
|
|
||||||
install:
|
|
||||||
- set PATH="%PYTHON%\\Scripts";%PATH%
|
|
||||||
- "%PYTHON%\\python.exe -m pip install --upgrade pip setuptools wheel"
|
|
||||||
- "%PYTHON%\\python.exe -m pip install -r requirements.txt"
|
|
||||||
- "%PYTHON%\\python.exe -m pip install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip"
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- "%PYTHON%\\python.exe setup.py build_binary"
|
|
||||||
|
|
||||||
artifacts:
|
|
||||||
- path: dist\KCC*
|
|
||||||
221
gui/KCC.ui
221
gui/KCC.ui
@@ -37,36 +37,6 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item row="5" column="1">
|
|
||||||
<widget class="QCheckBox" name="deleteBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Delete input file(s) or directory. It's not recoverable!</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Delete input</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QCheckBox" name="mangaBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Manga mode</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QCheckBox" name="outputSplit">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Output split</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="2">
|
<item row="4" column="2">
|
||||||
<widget class="QCheckBox" name="croppingBox">
|
<widget class="QCheckBox" name="croppingBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
@@ -80,39 +50,23 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QCheckBox" name="mozJpegBox">
|
<widget class="QCheckBox" name="mangaBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>JPEG/PNG/mozJpeg</string>
|
<string>Manga mode</string>
|
||||||
</property>
|
|
||||||
<property name="tristate">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="0">
|
||||||
<widget class="QCheckBox" name="upscaleBox">
|
<widget class="QCheckBox" name="webtoonBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Stretch/Upscale</string>
|
<string>Webtoon mode</string>
|
||||||
</property>
|
|
||||||
<property name="tristate">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="2">
|
|
||||||
<widget class="QCheckBox" name="colorBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Color mode</string>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -129,46 +83,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="2">
|
|
||||||
<widget class="QCheckBox" name="gammaBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p style='white-space:pre'>Disable automatic gamma correction.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Custom gamma</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="0">
|
|
||||||
<widget class="QCheckBox" name="spreadShiftBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Shift first page to opposite side in landscape for two page spread alignment</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Spread shift</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QCheckBox" name="webtoonBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Webtoon mode</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="1">
|
|
||||||
<widget class="QCheckBox" name="maximizeStrips">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>1x4 to 2x2 strips</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QCheckBox" name="borderBox">
|
<widget class="QCheckBox" name="borderBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
@@ -182,13 +96,36 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="1">
|
<item row="2" column="2">
|
||||||
<widget class="QCheckBox" name="noRotateBox">
|
<widget class="QCheckBox" name="gammaBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Do not rotate double page spreads in spread splitter option.</string>
|
<string><html><head/><body><p style='white-space:pre'>Disable automatic gamma correction.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>No rotate</string>
|
<string>Custom gamma</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="2">
|
||||||
|
<widget class="QCheckBox" name="interPanelCropBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled<br/></span>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Inter-panel crop</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="2">
|
||||||
|
<widget class="QCheckBox" name="colorBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Color mode</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -208,13 +145,23 @@
|
|||||||
<item row="5" column="2">
|
<item row="5" column="2">
|
||||||
<widget class="QCheckBox" name="disableProcessingBox">
|
<widget class="QCheckBox" name="disableProcessingBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><pre style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Do not process any image, ignore profile and processing options</pre></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Disable processing</string>
|
<string>Disable processing</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QCheckBox" name="maximizeStrips">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>1x4 to 2x2 strips</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLineEdit" name="authorEdit">
|
<widget class="QLineEdit" name="authorEdit">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@@ -237,6 +184,82 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QCheckBox" name="deleteBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Delete input file(s) or directory. It's not recoverable!</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Delete input</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QCheckBox" name="mozJpegBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>JPEG/PNG/mozJpeg</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QCheckBox" name="spreadShiftBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Shift first page to opposite side in landscape for two page spread alignment</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Spread shift</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QCheckBox" name="upscaleBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Stretch/Upscale</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QCheckBox" name="outputSplit">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Output split</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QCheckBox" name="noRotateBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Do not rotate double page spreads in spread splitter option.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>No rotate</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="2">
|
||||||
|
<widget class="QCheckBox" name="reduceRainbowBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Reduce rainbow effect on color eink by slightly blurring images</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Reduce Rainbow</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
39
kcc.spec
Normal file
39
kcc.spec
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
|
||||||
|
a = Analysis(['kcc.py'],
|
||||||
|
pathex=['.'],
|
||||||
|
binaries=[],
|
||||||
|
datas=[],
|
||||||
|
hiddenimports=['_cffi_backend'],
|
||||||
|
hookspath=[],
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
|
noarchive=False)
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data,
|
||||||
|
cipher=block_cipher)
|
||||||
|
|
||||||
|
exe = EXE(pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='kcc',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=False,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=False,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None , icon='icons\\comic2ebook.ico')
|
||||||
@@ -37,7 +37,7 @@ from packaging.version import Version
|
|||||||
from raven import Client
|
from raven import Client
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
|
|
||||||
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run
|
from .shared import HTMLStripper, available_archive_tools, sanitizeTrace, walkLevel, subprocess_run
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from . import comic2ebook
|
from . import comic2ebook
|
||||||
from . import metadata
|
from . import metadata
|
||||||
@@ -244,6 +244,7 @@ class WorkerThread(QThread):
|
|||||||
options.cropping = GUI.croppingBox.checkState().value
|
options.cropping = GUI.croppingBox.checkState().value
|
||||||
if GUI.croppingBox.checkState() != Qt.CheckState.Unchecked:
|
if GUI.croppingBox.checkState() != Qt.CheckState.Unchecked:
|
||||||
options.croppingp = float(GUI.croppingPowerValue)
|
options.croppingp = float(GUI.croppingPowerValue)
|
||||||
|
options.interpanelcrop = GUI.interPanelCropBox.checkState().value
|
||||||
if GUI.borderBox.checkState() == Qt.CheckState.PartiallyChecked:
|
if GUI.borderBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||||
options.white_borders = True
|
options.white_borders = True
|
||||||
elif GUI.borderBox.checkState() == Qt.CheckState.Checked:
|
elif GUI.borderBox.checkState() == Qt.CheckState.Checked:
|
||||||
@@ -252,6 +253,8 @@ class WorkerThread(QThread):
|
|||||||
options.batchsplit = 2
|
options.batchsplit = 2
|
||||||
if GUI.colorBox.isChecked():
|
if GUI.colorBox.isChecked():
|
||||||
options.forcecolor = True
|
options.forcecolor = True
|
||||||
|
if GUI.reduceRainbowBox.isChecked():
|
||||||
|
options.reducerainbow = True
|
||||||
if GUI.maximizeStrips.isChecked():
|
if GUI.maximizeStrips.isChecked():
|
||||||
options.maximizestrips = True
|
options.maximizestrips = True
|
||||||
if GUI.disableProcessingBox.isChecked():
|
if GUI.disableProcessingBox.isChecked():
|
||||||
@@ -314,13 +317,8 @@ class WorkerThread(QThread):
|
|||||||
GUI.progress.content = ''
|
GUI.progress.content = ''
|
||||||
self.errors = True
|
self.errors = True
|
||||||
_, _, traceback = sys.exc_info()
|
_, _, traceback = sys.exc_info()
|
||||||
if len(err.args) == 1:
|
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
||||||
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
|
||||||
else:
|
|
||||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
|
||||||
% (jobargv[-1], str(err.args[0]), err.args[1]), 'error')
|
|
||||||
GUI.sentry.extra_context({'realTraceback': err.args[1]})
|
|
||||||
if ' is corrupted.' not in str(err):
|
if ' is corrupted.' not in str(err):
|
||||||
GUI.sentry.captureException()
|
GUI.sentry.captureException()
|
||||||
MW.addMessage.emit('Error during conversion! Please consult '
|
MW.addMessage.emit('Error during conversion! Please consult '
|
||||||
@@ -789,11 +787,13 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
'gammaBox': GUI.gammaBox.checkState().value,
|
'gammaBox': GUI.gammaBox.checkState().value,
|
||||||
'croppingBox': GUI.croppingBox.checkState().value,
|
'croppingBox': GUI.croppingBox.checkState().value,
|
||||||
'croppingPowerSlider': float(self.croppingPowerValue) * 100,
|
'croppingPowerSlider': float(self.croppingPowerValue) * 100,
|
||||||
|
'interPanelCropBox': GUI.interPanelCropBox.checkState().value,
|
||||||
'upscaleBox': GUI.upscaleBox.checkState().value,
|
'upscaleBox': GUI.upscaleBox.checkState().value,
|
||||||
'borderBox': GUI.borderBox.checkState().value,
|
'borderBox': GUI.borderBox.checkState().value,
|
||||||
'webtoonBox': GUI.webtoonBox.checkState().value,
|
'webtoonBox': GUI.webtoonBox.checkState().value,
|
||||||
'outputSplit': GUI.outputSplit.checkState().value,
|
'outputSplit': GUI.outputSplit.checkState().value,
|
||||||
'colorBox': GUI.colorBox.checkState().value,
|
'colorBox': GUI.colorBox.checkState().value,
|
||||||
|
'reduceRainbowBox': GUI.reduceRainbowBox.checkState().value,
|
||||||
'disableProcessingBox': GUI.disableProcessingBox.checkState().value,
|
'disableProcessingBox': GUI.disableProcessingBox.checkState().value,
|
||||||
'mozJpegBox': GUI.mozJpegBox.checkState().value,
|
'mozJpegBox': GUI.mozJpegBox.checkState().value,
|
||||||
'widthBox': GUI.widthBox.value(),
|
'widthBox': GUI.widthBox.value(),
|
||||||
@@ -1066,19 +1066,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
self.addMessage('Since you are a new user of <b>KCC</b> please see few '
|
self.addMessage('Since you are a new user of <b>KCC</b> please see few '
|
||||||
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
||||||
'info')
|
'info')
|
||||||
try:
|
|
||||||
subprocess_run(['tar'], stdout=PIPE, stderr=STDOUT)
|
self.tar = 'tar' in available_archive_tools()
|
||||||
self.tar = True
|
self.sevenzip = '7z' in available_archive_tools()
|
||||||
except FileNotFoundError:
|
if not any([self.tar, self.sevenzip]):
|
||||||
self.tar = False
|
self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
|
||||||
try:
|
' to enable CBZ/CBR/ZIP/etc processing.', 'warning')
|
||||||
subprocess_run(['7z'], stdout=PIPE, stderr=STDOUT)
|
|
||||||
self.sevenzip = True
|
|
||||||
except FileNotFoundError:
|
|
||||||
self.sevenzip = False
|
|
||||||
if not self.tar:
|
|
||||||
self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
|
|
||||||
' to enable CBZ/CBR/ZIP/etc processing.', 'warning')
|
|
||||||
self.detectKindleGen(True)
|
self.detectKindleGen(True)
|
||||||
|
|
||||||
APP.messageFromOtherInstance.connect(self.handleMessage)
|
APP.messageFromOtherInstance.connect(self.handleMessage)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Resource object code (Python 3)
|
# Resource object code (Python 3)
|
||||||
# Created by: object code
|
# Created by: object code
|
||||||
# Created by: The Resource Compiler for Qt version 6.8.1
|
# Created by: The Resource Compiler for Qt version 6.8.2
|
||||||
# WARNING! All changes made in this file will be lost!
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
from PySide6 import QtCore
|
from PySide6 import QtCore
|
||||||
@@ -11612,51 +11612,51 @@ qt_resource_struct = b"\
|
|||||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x07\
|
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x07\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x02.\xed\
|
\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x02.\xed\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x03\
|
\x00\x00\x01\x95\x8bV),\
|
||||||
\x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x02\x83\x87\
|
\x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x02\x83\x87\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x00\
|
\x00\x00\x01\x95\x8bV)&\
|
||||||
\x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x02Y\x8c\
|
\x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x02Y\x8c\
|
||||||
\x00\x00\x01\x90(\xef\xc3\xff\
|
\x00\x00\x01\x95\x8bV)&\
|
||||||
\x00\x00\x01\xd6\x00\x00\x00\x00\x00\x01\x00\x02N)\
|
\x00\x00\x01\xd6\x00\x00\x00\x00\x00\x01\x00\x02N)\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x01\
|
\x00\x00\x01\x95\x8bV),\
|
||||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x0c\
|
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x0c\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x01(\x97\
|
\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x01(\x97\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x03\
|
\x00\x00\x01\x95\x8bV),\
|
||||||
\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x011\xef\
|
\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x011\xef\
|
||||||
\x00\x00\x01\x94o>\xe74\
|
\x00\x00\x01\x95\x8bV)5\
|
||||||
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x01\x1d\x90\
|
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x01\x1d\x90\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x02\
|
\x00\x00\x01\x95\x8bV),\
|
||||||
\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x01:\x05\
|
\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x01:\x05\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x04\
|
\x00\x00\x01\x95\x8bV)4\
|
||||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x03\x00\x00\x00\x11\
|
\x00\x00\x00X\x00\x02\x00\x00\x00\x03\x00\x00\x00\x11\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x02B\x00\x00\x00\x00\x00\x01\x00\x02\xb5\xd3\
|
\x00\x00\x02B\x00\x00\x00\x00\x00\x01\x00\x02\xb5\xd3\
|
||||||
\x00\x00\x01\x90(\xef\xc4!\
|
\x00\x00\x01\x95\x8bV)b\
|
||||||
\x00\x00\x02\x14\x00\x00\x00\x00\x00\x01\x00\x02\x9f\xd6\
|
\x00\x00\x02\x14\x00\x00\x00\x00\x00\x01\x00\x02\x9f\xd6\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x1d\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x02*\x00\x00\x00\x00\x00\x01\x00\x02\xa93\
|
\x00\x00\x02*\x00\x00\x00\x00\x00\x01\x00\x02\xa93\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x19\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x07\x00\x00\x00\x15\
|
\x00\x00\x00X\x00\x02\x00\x00\x00\x07\x00\x00\x00\x15\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x01P\xb1\
|
\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x01P\xb1\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x22\
|
\x00\x00\x01\x95\x8bV)b\
|
||||||
\x00\x00\x012\x00\x00\x00\x00\x00\x01\x00\x01yY\
|
\x00\x00\x012\x00\x00\x00\x00\x00\x01\x00\x01yY\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x1c\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x01\xd2-\
|
\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x01\xd2-\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x1e\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x01z\x00\x00\x00\x00\x00\x01\x00\x01\x8c\xe6\
|
\x00\x00\x01z\x00\x00\x00\x00\x00\x01\x00\x01\x8c\xe6\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x18\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x01LR\
|
\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x01LR\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x0e\
|
\x00\x00\x01\x95\x8bV)@\
|
||||||
\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x01?\xe9\
|
\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x01?\xe9\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x17\
|
\x00\x00\x01\x95\x8bV)Q\
|
||||||
\x00\x00\x01T\x00\x00\x00\x00\x00\x01\x00\x01\x82\xb0\
|
\x00\x00\x01T\x00\x00\x00\x00\x00\x01\x00\x01\x82\xb0\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x18\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1d\
|
\x00\x00\x00X\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1d\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x00h\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
\x00\x00\x00h\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||||
\x00\x00\x01\x90(\xef\xc4\x16\
|
\x00\x00\x01\x95\x8bV)Q\
|
||||||
"
|
"
|
||||||
|
|
||||||
def qInitResources():
|
def qInitResources():
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
################################################################################
|
################################################################################
|
||||||
## Form generated from reading UI file 'KCC.ui'
|
## Form generated from reading UI file 'KCC.ui'
|
||||||
##
|
##
|
||||||
## Created by: Qt User Interface Compiler version 6.8.1
|
## Created by: Qt User Interface Compiler version 6.8.2
|
||||||
##
|
##
|
||||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -40,43 +40,21 @@ class Ui_mainWindow(object):
|
|||||||
self.gridLayout_2 = QGridLayout(self.optionWidget)
|
self.gridLayout_2 = QGridLayout(self.optionWidget)
|
||||||
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
||||||
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
|
||||||
self.deleteBox = QCheckBox(self.optionWidget)
|
|
||||||
self.deleteBox.setObjectName(u"deleteBox")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
|
|
||||||
|
|
||||||
self.mangaBox = QCheckBox(self.optionWidget)
|
|
||||||
self.mangaBox.setObjectName(u"mangaBox")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
|
|
||||||
|
|
||||||
self.outputSplit = QCheckBox(self.optionWidget)
|
|
||||||
self.outputSplit.setObjectName(u"outputSplit")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
|
|
||||||
|
|
||||||
self.croppingBox = QCheckBox(self.optionWidget)
|
self.croppingBox = QCheckBox(self.optionWidget)
|
||||||
self.croppingBox.setObjectName(u"croppingBox")
|
self.croppingBox.setObjectName(u"croppingBox")
|
||||||
self.croppingBox.setTristate(True)
|
self.croppingBox.setTristate(True)
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
|
||||||
|
|
||||||
self.mozJpegBox = QCheckBox(self.optionWidget)
|
self.mangaBox = QCheckBox(self.optionWidget)
|
||||||
self.mozJpegBox.setObjectName(u"mozJpegBox")
|
self.mangaBox.setObjectName(u"mangaBox")
|
||||||
self.mozJpegBox.setTristate(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
|
||||||
|
|
||||||
self.upscaleBox = QCheckBox(self.optionWidget)
|
self.webtoonBox = QCheckBox(self.optionWidget)
|
||||||
self.upscaleBox.setObjectName(u"upscaleBox")
|
self.webtoonBox.setObjectName(u"webtoonBox")
|
||||||
self.upscaleBox.setTristate(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1)
|
||||||
|
|
||||||
self.colorBox = QCheckBox(self.optionWidget)
|
|
||||||
self.colorBox.setObjectName(u"colorBox")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1)
|
|
||||||
|
|
||||||
self.rotateBox = QCheckBox(self.optionWidget)
|
self.rotateBox = QCheckBox(self.optionWidget)
|
||||||
self.rotateBox.setObjectName(u"rotateBox")
|
self.rotateBox.setObjectName(u"rotateBox")
|
||||||
@@ -84,36 +62,27 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
|
||||||
|
|
||||||
self.gammaBox = QCheckBox(self.optionWidget)
|
|
||||||
self.gammaBox.setObjectName(u"gammaBox")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
|
|
||||||
|
|
||||||
self.spreadShiftBox = QCheckBox(self.optionWidget)
|
|
||||||
self.spreadShiftBox.setObjectName(u"spreadShiftBox")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1)
|
|
||||||
|
|
||||||
self.webtoonBox = QCheckBox(self.optionWidget)
|
|
||||||
self.webtoonBox.setObjectName(u"webtoonBox")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1)
|
|
||||||
|
|
||||||
self.maximizeStrips = QCheckBox(self.optionWidget)
|
|
||||||
self.maximizeStrips.setObjectName(u"maximizeStrips")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1)
|
|
||||||
|
|
||||||
self.borderBox = QCheckBox(self.optionWidget)
|
self.borderBox = QCheckBox(self.optionWidget)
|
||||||
self.borderBox.setObjectName(u"borderBox")
|
self.borderBox.setObjectName(u"borderBox")
|
||||||
self.borderBox.setTristate(True)
|
self.borderBox.setTristate(True)
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1)
|
||||||
|
|
||||||
self.noRotateBox = QCheckBox(self.optionWidget)
|
self.gammaBox = QCheckBox(self.optionWidget)
|
||||||
self.noRotateBox.setObjectName(u"noRotateBox")
|
self.gammaBox.setObjectName(u"gammaBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
|
||||||
|
|
||||||
|
self.interPanelCropBox = QCheckBox(self.optionWidget)
|
||||||
|
self.interPanelCropBox.setObjectName(u"interPanelCropBox")
|
||||||
|
self.interPanelCropBox.setTristate(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1)
|
||||||
|
|
||||||
|
self.colorBox = QCheckBox(self.optionWidget)
|
||||||
|
self.colorBox.setObjectName(u"colorBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1)
|
||||||
|
|
||||||
self.qualityBox = QCheckBox(self.optionWidget)
|
self.qualityBox = QCheckBox(self.optionWidget)
|
||||||
self.qualityBox.setObjectName(u"qualityBox")
|
self.qualityBox.setObjectName(u"qualityBox")
|
||||||
@@ -126,6 +95,11 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1)
|
||||||
|
|
||||||
|
self.maximizeStrips = QCheckBox(self.optionWidget)
|
||||||
|
self.maximizeStrips.setObjectName(u"maximizeStrips")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1)
|
||||||
|
|
||||||
self.authorEdit = QLineEdit(self.optionWidget)
|
self.authorEdit = QLineEdit(self.optionWidget)
|
||||||
self.authorEdit.setObjectName(u"authorEdit")
|
self.authorEdit.setObjectName(u"authorEdit")
|
||||||
sizePolicy = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
sizePolicy = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||||
@@ -138,6 +112,43 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.gridLayout_2.addWidget(self.authorEdit, 0, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.authorEdit, 0, 0, 1, 1)
|
||||||
|
|
||||||
|
self.deleteBox = QCheckBox(self.optionWidget)
|
||||||
|
self.deleteBox.setObjectName(u"deleteBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
|
||||||
|
|
||||||
|
self.mozJpegBox = QCheckBox(self.optionWidget)
|
||||||
|
self.mozJpegBox.setObjectName(u"mozJpegBox")
|
||||||
|
self.mozJpegBox.setTristate(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1)
|
||||||
|
|
||||||
|
self.spreadShiftBox = QCheckBox(self.optionWidget)
|
||||||
|
self.spreadShiftBox.setObjectName(u"spreadShiftBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1)
|
||||||
|
|
||||||
|
self.upscaleBox = QCheckBox(self.optionWidget)
|
||||||
|
self.upscaleBox.setObjectName(u"upscaleBox")
|
||||||
|
self.upscaleBox.setTristate(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1)
|
||||||
|
|
||||||
|
self.outputSplit = QCheckBox(self.optionWidget)
|
||||||
|
self.outputSplit.setObjectName(u"outputSplit")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
|
||||||
|
|
||||||
|
self.noRotateBox = QCheckBox(self.optionWidget)
|
||||||
|
self.noRotateBox.setObjectName(u"noRotateBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
|
||||||
|
|
||||||
|
self.reduceRainbowBox = QCheckBox(self.optionWidget)
|
||||||
|
self.reduceRainbowBox.setObjectName(u"reduceRainbowBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.reduceRainbowBox, 7, 2, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
||||||
|
|
||||||
@@ -377,73 +388,81 @@ class Ui_mainWindow(object):
|
|||||||
def retranslateUi(self, mainWindow):
|
def retranslateUi(self, mainWindow):
|
||||||
mainWindow.setWindowTitle(QCoreApplication.translate("mainWindow", u"Kindle Comic Converter", None))
|
mainWindow.setWindowTitle(QCoreApplication.translate("mainWindow", u"Kindle Comic Converter", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None))
|
self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Margins<br/></span>Margins</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None))
|
self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>", None))
|
self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Manga mode", None))
|
self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Manga mode", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>", None))
|
self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
|
self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Margins<br/></span>Margins</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>", None))
|
self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None))
|
self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None))
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Disable automatic gamma correction.</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>", None))
|
self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None))
|
self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None))
|
self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Disable automatic gamma correction.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None))
|
self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled<br/></span>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>", None))
|
self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None))
|
self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><pre style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Do not process any image, ignore profile and processing options</pre></body></html>", None))
|
self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
|
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None))
|
self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None))
|
self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.reduceRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Reduce rainbow effect on color eink by slightly blurring images", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.reduceRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Reduce Rainbow", None))
|
||||||
self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None))
|
self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None))
|
||||||
self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None))
|
self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
################################################################################
|
################################################################################
|
||||||
## Form generated from reading UI file 'MetaEditor.ui'
|
## Form generated from reading UI file 'MetaEditor.ui'
|
||||||
##
|
##
|
||||||
## Created by: Qt User Interface Compiler version 6.8.1
|
## Created by: Qt User Interface Compiler version 6.8.2
|
||||||
##
|
##
|
||||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '7.1.2'
|
__version__ = '7.3.3'
|
||||||
__license__ = 'ISC'
|
__license__ = 'ISC'
|
||||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import pathlib
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from time import strftime, gmtime
|
from time import perf_counter, strftime, gmtime
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from glob import glob, escape
|
from glob import glob, escape
|
||||||
from re import sub
|
from re import sub
|
||||||
@@ -33,14 +33,14 @@ from tempfile import mkdtemp, gettempdir, TemporaryFile
|
|||||||
from shutil import move, copytree, rmtree, copyfile
|
from shutil import move, copytree, rmtree, copyfile
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from natsort import os_sorted
|
from natsort import os_sort_keygen
|
||||||
from slugify import slugify as slugify_ext
|
from slugify import slugify as slugify_ext
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
from subprocess import STDOUT, PIPE
|
from subprocess import STDOUT, PIPE
|
||||||
from psutil import virtual_memory, disk_usage
|
from psutil import virtual_memory, disk_usage
|
||||||
from html import escape as hescape
|
from html import escape as hescape
|
||||||
|
|
||||||
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
from .shared import available_archive_tools, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
||||||
from . import comic2panel
|
from . import comic2panel
|
||||||
from . import image
|
from . import image
|
||||||
from . import comicarchive
|
from . import comicarchive
|
||||||
@@ -51,7 +51,7 @@ from . import kindle
|
|||||||
from . import __version__
|
from . import __version__
|
||||||
|
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
OS_SORT_KEY = os_sort_keygen()
|
||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
global options
|
global options
|
||||||
@@ -78,14 +78,14 @@ def main(argv=None):
|
|||||||
|
|
||||||
|
|
||||||
def buildHTML(path, imgfile, imgfilepath):
|
def buildHTML(path, imgfile, imgfilepath):
|
||||||
imgfilepath = md5Checksum(imgfilepath)
|
key = pathlib.Path(imgfilepath).name
|
||||||
filename = getImageFileName(imgfile)
|
filename = getImageFileName(imgfile)
|
||||||
deviceres = options.profileData[1]
|
deviceres = options.profileData[1]
|
||||||
if not options.noprocessing and "Rotated" in options.imgMetadata[imgfilepath]:
|
if not options.noprocessing and "Rotated" in options.imgMetadata[key]:
|
||||||
rotatedPage = True
|
rotatedPage = True
|
||||||
else:
|
else:
|
||||||
rotatedPage = False
|
rotatedPage = False
|
||||||
if not options.noprocessing and "BlackBackground" in options.imgMetadata[imgfilepath]:
|
if not options.noprocessing and "BlackBackground" in options.imgMetadata[key]:
|
||||||
additionalStyle = 'background-color:#000000;'
|
additionalStyle = 'background-color:#000000;'
|
||||||
else:
|
else:
|
||||||
additionalStyle = ''
|
additionalStyle = ''
|
||||||
@@ -216,7 +216,7 @@ def buildNCX(dstdir, title, chapters, chapternames):
|
|||||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||||
navID = folder.replace('/', '_').replace('\\', '_')
|
navID = folder.replace('/', '_').replace('\\', '_')
|
||||||
if options.chapters:
|
if options.comicinfo_chapters:
|
||||||
title = chapternames[chapter[1]]
|
title = chapternames[chapter[1]]
|
||||||
navID = filename[0].replace('/', '_').replace('\\', '_')
|
navID = filename[0].replace('/', '_').replace('\\', '_')
|
||||||
elif os.path.basename(folder) != "Text":
|
elif os.path.basename(folder) != "Text":
|
||||||
@@ -244,7 +244,7 @@ def buildNAV(dstdir, title, chapters, chapternames):
|
|||||||
for chapter in chapters:
|
for chapter in chapters:
|
||||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||||
if options.chapters:
|
if options.comicinfo_chapters:
|
||||||
title = chapternames[chapter[1]]
|
title = chapternames[chapter[1]]
|
||||||
elif os.path.basename(folder) != "Text":
|
elif os.path.basename(folder) != "Text":
|
||||||
title = chapternames[os.path.basename(folder)]
|
title = chapternames[os.path.basename(folder)]
|
||||||
@@ -256,7 +256,7 @@ def buildNAV(dstdir, title, chapters, chapternames):
|
|||||||
for chapter in chapters:
|
for chapter in chapters:
|
||||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||||
if options.chapters:
|
if options.comicinfo_chapters:
|
||||||
title = chapternames[chapter[1]]
|
title = chapternames[chapter[1]]
|
||||||
elif os.path.basename(folder) != "Text":
|
elif os.path.basename(folder) != "Text":
|
||||||
title = chapternames[os.path.basename(folder)]
|
title = chapternames[os.path.basename(folder)]
|
||||||
@@ -358,19 +358,19 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
|||||||
pageside = "right"
|
pageside = "right"
|
||||||
for entry in reflist:
|
for entry in reflist:
|
||||||
if options.righttoleft:
|
if options.righttoleft:
|
||||||
if entry.endswith("-a"):
|
if entry.endswith("-kcc-a"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("center"))
|
pageSpreadProperty("center"))
|
||||||
)
|
)
|
||||||
pageside = "right"
|
pageside = "right"
|
||||||
elif entry.endswith("-b"):
|
elif entry.endswith("-kcc-b"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("right"))
|
pageSpreadProperty("right"))
|
||||||
)
|
)
|
||||||
pageside = "right"
|
pageside = "right"
|
||||||
elif entry.endswith("-c"):
|
elif entry.endswith("-kcc-c"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("left"))
|
pageSpreadProperty("left"))
|
||||||
@@ -386,19 +386,19 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
|||||||
else:
|
else:
|
||||||
pageside = "right"
|
pageside = "right"
|
||||||
else:
|
else:
|
||||||
if entry.endswith("-a"):
|
if entry.endswith("-kcc-a"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("center"))
|
pageSpreadProperty("center"))
|
||||||
)
|
)
|
||||||
pageside = "left"
|
pageside = "left"
|
||||||
elif entry.endswith("-b"):
|
elif entry.endswith("-kcc-b"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("left"))
|
pageSpreadProperty("left"))
|
||||||
)
|
)
|
||||||
pageside = "left"
|
pageside = "left"
|
||||||
elif entry.endswith("-c"):
|
elif entry.endswith("-kcc-c"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("right"))
|
pageSpreadProperty("right"))
|
||||||
@@ -425,7 +425,6 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
|||||||
"</container>"])
|
"</container>"])
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
def buildEPUB(path, chapternames, tomenumber, ischunked):
|
def buildEPUB(path, chapternames, tomenumber, ischunked):
|
||||||
filelist = []
|
filelist = []
|
||||||
chapterlist = []
|
chapterlist = []
|
||||||
@@ -506,6 +505,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked):
|
|||||||
"display: none;\n",
|
"display: none;\n",
|
||||||
"}\n"])
|
"}\n"])
|
||||||
f.close()
|
f.close()
|
||||||
|
build_html_start = perf_counter()
|
||||||
for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||||
chapter = False
|
chapter = False
|
||||||
dirnames, filenames = walkSort(dirnames, filenames)
|
dirnames, filenames = walkSort(dirnames, filenames)
|
||||||
@@ -515,12 +515,17 @@ def buildEPUB(path, chapternames, tomenumber, ischunked):
|
|||||||
'cover' + getImageFileName(afile)[1])
|
'cover' + getImageFileName(afile)[1])
|
||||||
options.covers.append((image.Cover(os.path.join(dirpath, afile), cover, options,
|
options.covers.append((image.Cover(os.path.join(dirpath, afile), cover, options,
|
||||||
tomenumber), options.uuid))
|
tomenumber), options.uuid))
|
||||||
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
|
|
||||||
if not chapter:
|
if not chapter:
|
||||||
chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1]))
|
chapterlist.append((dirpath.replace('Images', 'Text'), afile))
|
||||||
chapter = True
|
chapter = True
|
||||||
|
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
|
||||||
|
build_html_end = perf_counter()
|
||||||
|
print(f"buildHTML: {build_html_end - build_html_start} seconds")
|
||||||
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
|
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
|
||||||
if not chapternames and options.chapters and not ischunked:
|
if ischunked:
|
||||||
|
options.comicinfo_chapters = []
|
||||||
|
|
||||||
|
if not chapternames and options.comicinfo_chapters:
|
||||||
chapterlist = []
|
chapterlist = []
|
||||||
|
|
||||||
global_diff = 0
|
global_diff = 0
|
||||||
@@ -533,7 +538,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked):
|
|||||||
elif options.splitter == 2:
|
elif options.splitter == 2:
|
||||||
diff_delta = 2
|
diff_delta = 2
|
||||||
|
|
||||||
for aChapter in options.chapters:
|
for aChapter in options.comicinfo_chapters:
|
||||||
pageid = aChapter[0]
|
pageid = aChapter[0]
|
||||||
cur_diff = global_diff
|
cur_diff = global_diff
|
||||||
global_diff = 0
|
global_diff = 0
|
||||||
@@ -556,7 +561,6 @@ def imgDirectoryProcessing(path):
|
|||||||
workerPool = Pool(maxtasksperchild=100)
|
workerPool = Pool(maxtasksperchild=100)
|
||||||
workerOutput = []
|
workerOutput = []
|
||||||
options.imgMetadata = {}
|
options.imgMetadata = {}
|
||||||
options.imgOld = []
|
|
||||||
work = []
|
work = []
|
||||||
pagenumber = 0
|
pagenumber = 0
|
||||||
for dirpath, _, filenames in os.walk(path):
|
for dirpath, _, filenames in os.walk(path):
|
||||||
@@ -566,19 +570,19 @@ def imgDirectoryProcessing(path):
|
|||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit(str(pagenumber))
|
GUI.progressBarTick.emit(str(pagenumber))
|
||||||
if len(work) > 0:
|
if len(work) > 0:
|
||||||
|
img_processing_start = perf_counter()
|
||||||
for i in work:
|
for i in work:
|
||||||
workerPool.apply_async(func=imgFileProcessing, args=(i,), callback=imgFileProcessingTick)
|
workerPool.apply_async(func=imgFileProcessing, args=(i,), callback=imgFileProcessingTick)
|
||||||
workerPool.close()
|
workerPool.close()
|
||||||
workerPool.join()
|
workerPool.join()
|
||||||
|
img_processing_end = perf_counter()
|
||||||
|
print(f"imgFileProcessing: {img_processing_end - img_processing_start} seconds")
|
||||||
if GUI and not GUI.conversionAlive:
|
if GUI and not GUI.conversionAlive:
|
||||||
rmtree(os.path.join(path, '..', '..'), True)
|
rmtree(os.path.join(path, '..', '..'), True)
|
||||||
raise UserWarning("Conversion interrupted.")
|
raise UserWarning("Conversion interrupted.")
|
||||||
if len(workerOutput) > 0:
|
if len(workerOutput) > 0:
|
||||||
rmtree(os.path.join(path, '..', '..'), True)
|
rmtree(os.path.join(path, '..', '..'), True)
|
||||||
raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0][0], workerOutput[0][1])
|
raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0][0], workerOutput[0][1])
|
||||||
for file in options.imgOld:
|
|
||||||
if os.path.isfile(file):
|
|
||||||
os.remove(file)
|
|
||||||
else:
|
else:
|
||||||
rmtree(os.path.join(path, '..', '..'), True)
|
rmtree(os.path.join(path, '..', '..'), True)
|
||||||
raise UserWarning("Source directory is empty.")
|
raise UserWarning("Source directory is empty.")
|
||||||
@@ -592,7 +596,6 @@ def imgFileProcessingTick(output):
|
|||||||
for page in output:
|
for page in output:
|
||||||
if page is not None:
|
if page is not None:
|
||||||
options.imgMetadata[page[0]] = page[1]
|
options.imgMetadata[page[0]] = page[1]
|
||||||
options.imgOld.append(page[2])
|
|
||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit('tick')
|
GUI.progressBarTick.emit('tick')
|
||||||
if not GUI.conversionAlive:
|
if not GUI.conversionAlive:
|
||||||
@@ -612,8 +615,11 @@ def imgFileProcessing(work):
|
|||||||
img.cropPageNumber(opt.croppingp, opt.croppingm)
|
img.cropPageNumber(opt.croppingp, opt.croppingm)
|
||||||
if opt.cropping > 0 and not opt.webtoon:
|
if opt.cropping > 0 and not opt.webtoon:
|
||||||
img.cropMargin(opt.croppingp, opt.croppingm)
|
img.cropMargin(opt.croppingp, opt.croppingm)
|
||||||
|
if opt.interpanelcrop > 0:
|
||||||
|
img.cropInterPanelEmptySections("horizontal" if opt.interpanelcrop == 1 else "both")
|
||||||
img.autocontrastImage()
|
img.autocontrastImage()
|
||||||
img.resizeImage()
|
img.resizeImage()
|
||||||
|
img.optimizeForDisplay(opt.reducerainbow)
|
||||||
if opt.forcepng and not opt.forcecolor:
|
if opt.forcepng and not opt.forcecolor:
|
||||||
img.quantizeImage()
|
img.quantizeImage()
|
||||||
output.append(img.saveToDir())
|
output.append(img.saveToDir())
|
||||||
@@ -626,7 +632,7 @@ def getWorkFolder(afile):
|
|||||||
if os.path.isdir(afile):
|
if os.path.isdir(afile):
|
||||||
if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
|
if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
|
||||||
raise UserWarning("Not enough disk space to perform conversion.")
|
raise UserWarning("Not enough disk space to perform conversion.")
|
||||||
workdir = mkdtemp('', 'KCC-')
|
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
||||||
try:
|
try:
|
||||||
os.rmdir(workdir)
|
os.rmdir(workdir)
|
||||||
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
|
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
|
||||||
@@ -642,26 +648,34 @@ def getWorkFolder(afile):
|
|||||||
if afile.lower().endswith('.pdf'):
|
if afile.lower().endswith('.pdf'):
|
||||||
pdf = pdfjpgextract.PdfJpgExtract(afile)
|
pdf = pdfjpgextract.PdfJpgExtract(afile)
|
||||||
path, njpg = pdf.extract()
|
path, njpg = pdf.extract()
|
||||||
|
workdir = path
|
||||||
|
sanitizePermissions(path)
|
||||||
if njpg == 0:
|
if njpg == 0:
|
||||||
rmtree(path, True)
|
rmtree(path, True)
|
||||||
raise UserWarning("Failed to extract images from PDF file.")
|
raise UserWarning("Failed to extract images from PDF file.")
|
||||||
else:
|
else:
|
||||||
workdir = mkdtemp('', 'KCC-')
|
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
||||||
try:
|
try:
|
||||||
cbx = comicarchive.ComicArchive(afile)
|
cbx = comicarchive.ComicArchive(afile)
|
||||||
path = cbx.extract(workdir)
|
path = cbx.extract(workdir)
|
||||||
|
sanitizePermissions(path)
|
||||||
tdir = os.listdir(workdir)
|
tdir = os.listdir(workdir)
|
||||||
if 'ComicInfo.xml' in tdir:
|
if len(tdir) == 2 and 'ComicInfo.xml' in tdir:
|
||||||
tdir.remove('ComicInfo.xml')
|
tdir.remove('ComicInfo.xml')
|
||||||
|
if os.path.isdir(os.path.join(workdir, tdir[0])):
|
||||||
|
os.replace(
|
||||||
|
os.path.join(workdir, 'ComicInfo.xml'),
|
||||||
|
os.path.join(workdir, tdir[0], 'ComicInfo.xml')
|
||||||
|
)
|
||||||
|
if len(tdir) == 1 and os.path.isdir(os.path.join(workdir, tdir[0])):
|
||||||
|
path = os.path.join(workdir, tdir[0])
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
rmtree(workdir, True)
|
rmtree(workdir, True)
|
||||||
raise UserWarning(e)
|
raise UserWarning(e)
|
||||||
else:
|
else:
|
||||||
raise UserWarning("Failed to open source file/directory.")
|
raise UserWarning("Failed to open source file/directory.")
|
||||||
sanitizePermissions(path)
|
newpath = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
||||||
newpath = mkdtemp('', 'KCC-')
|
os.renames(path, os.path.join(newpath, 'OEBPS', 'Images'))
|
||||||
copytree(path, os.path.join(newpath, 'OEBPS', 'Images'))
|
|
||||||
rmtree(path, True)
|
|
||||||
return newpath
|
return newpath
|
||||||
|
|
||||||
|
|
||||||
@@ -702,7 +716,7 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
|||||||
|
|
||||||
def getComicInfo(path, originalpath):
|
def getComicInfo(path, originalpath):
|
||||||
xmlPath = os.path.join(path, 'ComicInfo.xml')
|
xmlPath = os.path.join(path, 'ComicInfo.xml')
|
||||||
options.chapters = []
|
options.comicinfo_chapters = []
|
||||||
options.summary = ''
|
options.summary = ''
|
||||||
titleSuffix = ''
|
titleSuffix = ''
|
||||||
if options.title == 'defaulttitle':
|
if options.title == 'defaulttitle':
|
||||||
@@ -725,9 +739,7 @@ def getComicInfo(path, originalpath):
|
|||||||
except Exception:
|
except Exception:
|
||||||
os.remove(xmlPath)
|
os.remove(xmlPath)
|
||||||
return
|
return
|
||||||
if xml.data['Title']:
|
if defaultTitle:
|
||||||
options.title = hescape(xml.data['Title'])
|
|
||||||
elif defaultTitle:
|
|
||||||
if xml.data['Series']:
|
if xml.data['Series']:
|
||||||
options.title = hescape(xml.data['Series'])
|
options.title = hescape(xml.data['Series'])
|
||||||
if xml.data['Volume']:
|
if xml.data['Volume']:
|
||||||
@@ -746,7 +758,7 @@ def getComicInfo(path, originalpath):
|
|||||||
else:
|
else:
|
||||||
options.authors = ['KCC']
|
options.authors = ['KCC']
|
||||||
if xml.data['Bookmarks']:
|
if xml.data['Bookmarks']:
|
||||||
options.chapters = xml.data['Bookmarks']
|
options.comicinfo_chapters = xml.data['Bookmarks']
|
||||||
if xml.data['Summary']:
|
if xml.data['Summary']:
|
||||||
options.summary = hescape(xml.data['Summary'])
|
options.summary = hescape(xml.data['Summary'])
|
||||||
os.remove(xmlPath)
|
os.remove(xmlPath)
|
||||||
@@ -779,22 +791,22 @@ def getPanelViewSize(deviceres, size):
|
|||||||
|
|
||||||
def sanitizeTree(filetree):
|
def sanitizeTree(filetree):
|
||||||
chapterNames = {}
|
chapterNames = {}
|
||||||
for root, dirs, files in os.walk(filetree, False):
|
page = 1
|
||||||
for i, name in enumerate(os_sorted(files)):
|
for root, dirs, files in os.walk(filetree):
|
||||||
|
dirs.sort(key=OS_SORT_KEY)
|
||||||
|
files.sort(key=OS_SORT_KEY)
|
||||||
|
for name in files:
|
||||||
splitname = os.path.splitext(name)
|
splitname = os.path.splitext(name)
|
||||||
|
|
||||||
# file needs kcc at front AND back to avoid renaming issues
|
# 9999 page limit
|
||||||
slugified = f'kcc-{i:04}'
|
slugified = f'kcc-{page:04}'
|
||||||
for suffix in '-KCC', '-KCC-A', '-KCC-B', '-KCC-C':
|
page += 1
|
||||||
if splitname[0].endswith(suffix):
|
|
||||||
slugified += suffix.lower()
|
|
||||||
break
|
|
||||||
|
|
||||||
newKey = os.path.join(root, slugified + splitname[1])
|
newKey = os.path.join(root, slugified + splitname[1])
|
||||||
key = os.path.join(root, name)
|
key = os.path.join(root, name)
|
||||||
if key != newKey:
|
if key != newKey:
|
||||||
os.replace(key, newKey)
|
os.replace(key, newKey)
|
||||||
for name in dirs:
|
for i, name in enumerate(dirs):
|
||||||
tmpName = name
|
tmpName = name
|
||||||
slugified = slugify(name)
|
slugified = slugify(name)
|
||||||
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
|
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
|
||||||
@@ -804,6 +816,7 @@ def sanitizeTree(filetree):
|
|||||||
key = os.path.join(root, name)
|
key = os.path.join(root, name)
|
||||||
if key != newKey:
|
if key != newKey:
|
||||||
os.replace(key, newKey)
|
os.replace(key, newKey)
|
||||||
|
dirs[i] = newKey
|
||||||
return chapterNames
|
return chapterNames
|
||||||
|
|
||||||
|
|
||||||
@@ -815,12 +828,11 @@ def sanitizePermissions(filetree):
|
|||||||
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC)
|
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC)
|
||||||
|
|
||||||
|
|
||||||
def splitDirectory(path):
|
def chunk_directory(path):
|
||||||
level = -1
|
level = -1
|
||||||
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||||
for f in files:
|
for f in files:
|
||||||
if f.endswith('.jpg') or f.endswith('.jpeg') or f.endswith('.png') or f.endswith('.gif') or \
|
if getImageFileName(f):
|
||||||
f.endswith('.webp'):
|
|
||||||
newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep)
|
newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep)
|
||||||
if level != -1 and level != newLevel:
|
if level != -1 and level != newLevel:
|
||||||
level = 0
|
level = 0
|
||||||
@@ -828,16 +840,17 @@ def splitDirectory(path):
|
|||||||
else:
|
else:
|
||||||
level = newLevel
|
level = newLevel
|
||||||
if level > 0:
|
if level > 0:
|
||||||
splitter = splitProcess(os.path.join(path, 'OEBPS', 'Images'), level)
|
parent = pathlib.Path(path).parent
|
||||||
|
chunker = chunk_process(os.path.join(path, 'OEBPS', 'Images'), level, parent)
|
||||||
path = [path]
|
path = [path]
|
||||||
for tome in splitter:
|
for tome in chunker:
|
||||||
path.append(tome)
|
path.append(tome)
|
||||||
return path
|
return path
|
||||||
else:
|
else:
|
||||||
raise UserWarning('Unsupported directory structure.')
|
raise UserWarning('Unsupported directory structure.')
|
||||||
|
|
||||||
|
|
||||||
def splitProcess(path, mode):
|
def chunk_process(path, mode, parent):
|
||||||
output = []
|
output = []
|
||||||
currentSize = 0
|
currentSize = 0
|
||||||
currentTarget = path
|
currentTarget = path
|
||||||
@@ -857,7 +870,7 @@ def splitProcess(path, mode):
|
|||||||
else:
|
else:
|
||||||
size = getDirectorySize(os.path.join(root, name))
|
size = getDirectorySize(os.path.join(root, name))
|
||||||
if currentSize + size > targetSize:
|
if currentSize + size > targetSize:
|
||||||
currentTarget, pathRoot = createNewTome()
|
currentTarget, pathRoot = createNewTome(parent)
|
||||||
output.append(pathRoot)
|
output.append(pathRoot)
|
||||||
currentSize = size
|
currentSize = size
|
||||||
else:
|
else:
|
||||||
@@ -869,15 +882,14 @@ def splitProcess(path, mode):
|
|||||||
for root, dirs, _ in walkLevel(path, 0):
|
for root, dirs, _ in walkLevel(path, 0):
|
||||||
for name in dirs:
|
for name in dirs:
|
||||||
if not firstTome:
|
if not firstTome:
|
||||||
currentTarget, pathRoot = createNewTome()
|
currentTarget, pathRoot = createNewTome(parent)
|
||||||
output.append(pathRoot)
|
output.append(pathRoot)
|
||||||
move(os.path.join(root, name), os.path.join(currentTarget, name))
|
move(os.path.join(root, name), os.path.join(currentTarget, name))
|
||||||
else:
|
else:
|
||||||
firstTome = False
|
firstTome = False
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def detectSuboptimalProcessing(tmppath, orgpath):
|
||||||
def detectCorruption(tmppath, orgpath):
|
|
||||||
imageNumber = 0
|
imageNumber = 0
|
||||||
imageSmaller = 0
|
imageSmaller = 0
|
||||||
alreadyProcessed = False
|
alreadyProcessed = False
|
||||||
@@ -893,9 +905,6 @@ def detectCorruption(tmppath, orgpath):
|
|||||||
raise RuntimeError('Image file %s is corrupted.' % pathOrg)
|
raise RuntimeError('Image file %s is corrupted.' % pathOrg)
|
||||||
try:
|
try:
|
||||||
img = Image.open(path)
|
img = Image.open(path)
|
||||||
img.verify()
|
|
||||||
img = Image.open(path)
|
|
||||||
img.load()
|
|
||||||
imageNumber += 1
|
imageNumber += 1
|
||||||
if options.profileData[1][0] > img.size[0] and options.profileData[1][1] > img.size[1]:
|
if options.profileData[1][0] > img.size[0] and options.profileData[1][1] > img.size[1]:
|
||||||
imageSmaller += 1
|
imageSmaller += 1
|
||||||
@@ -906,7 +915,10 @@ def detectCorruption(tmppath, orgpath):
|
|||||||
else:
|
else:
|
||||||
raise RuntimeError('Image file %s is corrupted. Error: %s' % (pathOrg, str(err)))
|
raise RuntimeError('Image file %s is corrupted. Error: %s' % (pathOrg, str(err)))
|
||||||
else:
|
else:
|
||||||
os.remove(os.path.join(root, name))
|
try:
|
||||||
|
os.remove(os.path.join(root, name))
|
||||||
|
except OSError as e:
|
||||||
|
raise RuntimeError(f"{name}: {e}")
|
||||||
if alreadyProcessed:
|
if alreadyProcessed:
|
||||||
print("WARNING: Source files are probably created by KCC. The second conversion will decrease quality.")
|
print("WARNING: Source files are probably created by KCC. The second conversion will decrease quality.")
|
||||||
if GUI:
|
if GUI:
|
||||||
@@ -922,8 +934,8 @@ def detectCorruption(tmppath, orgpath):
|
|||||||
GUI.addMessage.emit('', '', False)
|
GUI.addMessage.emit('', '', False)
|
||||||
|
|
||||||
|
|
||||||
def createNewTome():
|
def createNewTome(parent):
|
||||||
tomePathRoot = mkdtemp('', 'KCC-')
|
tomePathRoot = mkdtemp('', 'KCC-', parent)
|
||||||
tomePath = os.path.join(tomePathRoot, 'OEBPS', 'Images')
|
tomePath = os.path.join(tomePathRoot, 'OEBPS', 'Images')
|
||||||
os.makedirs(tomePath)
|
os.makedirs(tomePath)
|
||||||
return tomePath, tomePathRoot
|
return tomePath, tomePathRoot
|
||||||
@@ -936,17 +948,27 @@ def slugify(value):
|
|||||||
|
|
||||||
|
|
||||||
def makeZIP(zipfilename, basedir, isepub=False):
|
def makeZIP(zipfilename, basedir, isepub=False):
|
||||||
|
start = perf_counter()
|
||||||
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
||||||
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
if '7z' in available_archive_tools():
|
||||||
if isepub:
|
if isepub:
|
||||||
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
|
mimetypeFile = open(os.path.join(basedir, 'mimetype'), 'w')
|
||||||
for dirpath, _, filenames in os.walk(basedir):
|
mimetypeFile.write('application/epub+zip')
|
||||||
for name in filenames:
|
mimetypeFile.close()
|
||||||
path = os.path.normpath(os.path.join(dirpath, name))
|
subprocess_run(['7z', 'a', '-tzip', zipfilename, os.path.join(basedir, "*")], capture_output=True, check=True)
|
||||||
aPath = os.path.normpath(os.path.join(dirpath.replace(basedir, ''), name))
|
else:
|
||||||
if os.path.isfile(path):
|
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
||||||
zipOutput.write(path, aPath)
|
if isepub:
|
||||||
zipOutput.close()
|
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
|
||||||
|
for dirpath, _, filenames in os.walk(basedir):
|
||||||
|
for name in filenames:
|
||||||
|
path = os.path.normpath(os.path.join(dirpath, name))
|
||||||
|
aPath = os.path.normpath(os.path.join(dirpath.replace(basedir, ''), name))
|
||||||
|
if os.path.isfile(path):
|
||||||
|
zipOutput.write(path, aPath)
|
||||||
|
zipOutput.close()
|
||||||
|
end = perf_counter()
|
||||||
|
print(f"makeZIP time: {end - start} seconds")
|
||||||
return zipfilename
|
return zipfilename
|
||||||
|
|
||||||
|
|
||||||
@@ -1013,12 +1035,16 @@ def makeParser():
|
|||||||
help="Set cropping power [Default=1.0]")
|
help="Set cropping power [Default=1.0]")
|
||||||
processing_options.add_argument("--cm", "--croppingminimum", type=float, dest="croppingm", default="0.0",
|
processing_options.add_argument("--cm", "--croppingminimum", type=float, dest="croppingm", default="0.0",
|
||||||
help="Set cropping minimum area ratio [Default=0.0]")
|
help="Set cropping minimum area ratio [Default=0.0]")
|
||||||
|
processing_options.add_argument("--ipc", "--interpanelcrop", type=int, dest="interpanelcrop", default="0",
|
||||||
|
help="Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0]")
|
||||||
processing_options.add_argument("--blackborders", action="store_true", dest="black_borders", default=False,
|
processing_options.add_argument("--blackborders", action="store_true", dest="black_borders", default=False,
|
||||||
help="Disable autodetection and force black borders")
|
help="Disable autodetection and force black borders")
|
||||||
processing_options.add_argument("--whiteborders", action="store_true", dest="white_borders", default=False,
|
processing_options.add_argument("--whiteborders", action="store_true", dest="white_borders", default=False,
|
||||||
help="Disable autodetection and force white borders")
|
help="Disable autodetection and force white borders")
|
||||||
processing_options.add_argument("--forcecolor", action="store_true", dest="forcecolor", default=False,
|
processing_options.add_argument("--forcecolor", action="store_true", dest="forcecolor", default=False,
|
||||||
help="Don't convert images to grayscale")
|
help="Don't convert images to grayscale")
|
||||||
|
output_options.add_argument("--reducerainbow", action="store_true", dest="reducerainbow", default=False,
|
||||||
|
help="Reduce rainbow effect on color eink by slightly blurring images.")
|
||||||
processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False,
|
processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False,
|
||||||
help="Create PNG files instead JPEG")
|
help="Create PNG files instead JPEG")
|
||||||
processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
|
processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
|
||||||
@@ -1117,9 +1143,7 @@ def checkTools(source):
|
|||||||
source = source.upper()
|
source = source.upper()
|
||||||
if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \
|
if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \
|
||||||
source.endswith('.ZIP') or source.endswith('.CBZ'):
|
source.endswith('.ZIP') or source.endswith('.CBZ'):
|
||||||
try:
|
if '7z' not in available_archive_tools():
|
||||||
subprocess_run(['7z'], stdout=PIPE, stderr=STDOUT)
|
|
||||||
except FileNotFoundError:
|
|
||||||
print('ERROR: 7z is missing!')
|
print('ERROR: 7z is missing!')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if options.format == 'MOBI':
|
if options.format == 'MOBI':
|
||||||
@@ -1149,6 +1173,7 @@ def checkPre(source):
|
|||||||
|
|
||||||
|
|
||||||
def makeBook(source, qtgui=None):
|
def makeBook(source, qtgui=None):
|
||||||
|
start = perf_counter()
|
||||||
global GUI
|
global GUI
|
||||||
GUI = qtgui
|
GUI = qtgui
|
||||||
if GUI:
|
if GUI:
|
||||||
@@ -1160,7 +1185,8 @@ def makeBook(source, qtgui=None):
|
|||||||
path = getWorkFolder(source)
|
path = getWorkFolder(source)
|
||||||
print("Checking images...")
|
print("Checking images...")
|
||||||
getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
|
getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
|
||||||
detectCorruption(os.path.join(path, "OEBPS", "Images"), source)
|
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
|
||||||
|
chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||||
if options.webtoon:
|
if options.webtoon:
|
||||||
y = image.ProfileData.Profiles[options.profile][1][1]
|
y = image.ProfileData.Profiles[options.profile][1][1]
|
||||||
comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtgui)
|
comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtgui)
|
||||||
@@ -1173,9 +1199,8 @@ def makeBook(source, qtgui=None):
|
|||||||
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"))
|
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"))
|
||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit('1')
|
GUI.progressBarTick.emit('1')
|
||||||
chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
|
||||||
if options.batchsplit > 0:
|
if options.batchsplit > 0:
|
||||||
tomes = splitDirectory(path)
|
tomes = chunk_directory(path)
|
||||||
else:
|
else:
|
||||||
tomes = [path]
|
tomes = [path]
|
||||||
filepath = []
|
filepath = []
|
||||||
@@ -1251,6 +1276,8 @@ def makeBook(source, qtgui=None):
|
|||||||
elif os.path.isdir(source):
|
elif os.path.isdir(source):
|
||||||
rmtree(source)
|
rmtree(source)
|
||||||
|
|
||||||
|
end = perf_counter()
|
||||||
|
print(f"makeBook: {end - start} seconds")
|
||||||
return filepath
|
return filepath
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ class ComicArchive:
|
|||||||
['unar', self.filepath, '-f', '-o', targetdir]
|
['unar', self.filepath, '-f', '-o', targetdir]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
extraction_commands.reverse()
|
||||||
|
|
||||||
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
||||||
extraction_commands.append(
|
extraction_commands.append(
|
||||||
['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.filepath, targetdir]
|
['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.filepath, targetdir]
|
||||||
|
|||||||
28
kindlecomicconverter/common_crop.py
Normal file
28
kindlecomicconverter/common_crop.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
def threshold_from_power(power):
|
||||||
|
return 240-(power*64)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Groups close values together
|
||||||
|
'''
|
||||||
|
def group_close_values(vals, max_dist_tolerated):
|
||||||
|
groups = []
|
||||||
|
|
||||||
|
group_start = -1
|
||||||
|
group_end = 0
|
||||||
|
for i in range(len(vals)):
|
||||||
|
dist = vals[i] - group_end
|
||||||
|
if group_start == -1:
|
||||||
|
group_start = vals[i]
|
||||||
|
group_end = vals[i]
|
||||||
|
elif dist <= max_dist_tolerated:
|
||||||
|
group_end = vals[i]
|
||||||
|
else:
|
||||||
|
groups.append((group_start, group_end))
|
||||||
|
group_start = -1
|
||||||
|
group_end = -1
|
||||||
|
|
||||||
|
if group_start != -1:
|
||||||
|
groups.append((group_start, group_end))
|
||||||
|
|
||||||
|
return groups
|
||||||
@@ -20,10 +20,11 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import mozjpeg_lossless_optimization
|
import mozjpeg_lossless_optimization
|
||||||
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
||||||
from .shared import md5Checksum
|
|
||||||
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
|
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
|
||||||
|
|
||||||
AUTO_CROP_THRESHOLD = 0.015
|
AUTO_CROP_THRESHOLD = 0.015
|
||||||
|
|
||||||
@@ -140,7 +141,13 @@ class ComicPageParser:
|
|||||||
self.source = source
|
self.source = source
|
||||||
self.size = self.opt.profileData[1]
|
self.size = self.opt.profileData[1]
|
||||||
self.payload = []
|
self.payload = []
|
||||||
self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB')
|
|
||||||
|
# Detect corruption in source image, let caller catch any exceptions triggered.
|
||||||
|
srcImgPath = os.path.join(source[0], source[1])
|
||||||
|
self.image = Image.open(srcImgPath)
|
||||||
|
self.image.verify()
|
||||||
|
self.image = Image.open(srcImgPath).convert('RGB')
|
||||||
|
|
||||||
self.color = self.colorCheck()
|
self.color = self.colorCheck()
|
||||||
self.fill = self.fillCheck()
|
self.fill = self.fillCheck()
|
||||||
# backwards compatibility for Pillow >9.1.0
|
# backwards compatibility for Pillow >9.1.0
|
||||||
@@ -278,14 +285,14 @@ class ComicPage:
|
|||||||
self.rotated = False
|
self.rotated = False
|
||||||
self.orgPath = os.path.join(path[0], path[1])
|
self.orgPath = os.path.join(path[0], path[1])
|
||||||
if 'N' in mode:
|
if 'N' in mode:
|
||||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC'
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-kcc'
|
||||||
elif 'R' in mode:
|
elif 'R' in mode:
|
||||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-A'
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-kcc-a'
|
||||||
self.rotated = True
|
self.rotated = True
|
||||||
elif 'S1' in mode:
|
elif 'S1' in mode:
|
||||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-B'
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-kcc-b'
|
||||||
elif 'S2' in mode:
|
elif 'S2' in mode:
|
||||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-C'
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-kcc-c'
|
||||||
# backwards compatibility for Pillow >9.1.0
|
# backwards compatibility for Pillow >9.1.0
|
||||||
if not hasattr(Image, 'Resampling'):
|
if not hasattr(Image, 'Resampling'):
|
||||||
Image.Resampling = Image
|
Image.Resampling = Image
|
||||||
@@ -314,7 +321,9 @@ class ComicPage:
|
|||||||
output_jpeg_file.write(output_jpeg_bytes)
|
output_jpeg_file.write(output_jpeg_bytes)
|
||||||
else:
|
else:
|
||||||
self.image.save(self.targetPath, 'JPEG', optimize=1, quality=85)
|
self.image.save(self.targetPath, 'JPEG', optimize=1, quality=85)
|
||||||
return [md5Checksum(self.targetPath), flags, self.orgPath]
|
if os.path.isfile(self.orgPath):
|
||||||
|
os.remove(self.orgPath)
|
||||||
|
return [Path(self.targetPath).name, flags]
|
||||||
except IOError as err:
|
except IOError as err:
|
||||||
raise RuntimeError('Cannot save image. ' + str(err))
|
raise RuntimeError('Cannot save image. ' + str(err))
|
||||||
|
|
||||||
@@ -340,6 +349,14 @@ class ComicPage:
|
|||||||
# Quantize is deprecated but new function call it internally anyway...
|
# Quantize is deprecated but new function call it internally anyway...
|
||||||
self.image = self.image.quantize(palette=palImg)
|
self.image = self.image.quantize(palette=palImg)
|
||||||
|
|
||||||
|
def optimizeForDisplay(self, reducerainbow):
|
||||||
|
# Reduce rainbow artifacts for grayscale images by breaking up dither patterns that cause Moire interference with color filter array
|
||||||
|
if reducerainbow and not self.color:
|
||||||
|
unsharpFilter = ImageFilter.UnsharpMask(radius=1, percent=100)
|
||||||
|
self.image = self.image.filter(unsharpFilter)
|
||||||
|
self.image = self.image.filter(ImageFilter.BoxBlur(1.0))
|
||||||
|
self.image = self.image.filter(unsharpFilter)
|
||||||
|
|
||||||
def resizeImage(self):
|
def resizeImage(self):
|
||||||
# kindle scribe conversion to mobi is limited in resolution by kindlegen, same with send to kindle and epub
|
# kindle scribe conversion to mobi is limited in resolution by kindlegen, same with send to kindle and epub
|
||||||
if self.kindle_scribe_azw3:
|
if self.kindle_scribe_azw3:
|
||||||
@@ -390,6 +407,8 @@ class ComicPage:
|
|||||||
if bbox:
|
if bbox:
|
||||||
self.maybeCrop(bbox, minimum)
|
self.maybeCrop(bbox, minimum)
|
||||||
|
|
||||||
|
def cropInterPanelEmptySections(self, direction):
|
||||||
|
self.image = crop_empty_inter_panel(self.image, direction, background_color=self.fill)
|
||||||
|
|
||||||
class Cover:
|
class Cover:
|
||||||
def __init__(self, source, target, opt, tomeid):
|
def __init__(self, source, target, opt, tomeid):
|
||||||
|
|||||||
76
kindlecomicconverter/inter_panel_crop_alg.py
Normal file
76
kindlecomicconverter/inter_panel_crop_alg.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from PIL import Image, ImageFilter, ImageOps
|
||||||
|
import numpy as np
|
||||||
|
from typing import Literal
|
||||||
|
from .common_crop import threshold_from_power, group_close_values
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Crops inter-panel empty spaces (ignores empty spaces near borders - for that use crop margins).
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
img (PIL image): A PIL image.
|
||||||
|
direction (horizontal or vertical or both): To crop rows (horizontal), cols (vertical) or both.
|
||||||
|
keep (float): Distance to keep between panels after cropping (in percentage relative to the original distance).
|
||||||
|
background_color (string): 'white' for white background, anything else for black.
|
||||||
|
Returns:
|
||||||
|
img (PIL image): A PIL image after cropping empty sections.
|
||||||
|
'''
|
||||||
|
def crop_empty_inter_panel(img, direction: Literal["horizontal", "vertical", "both"], keep=0.04, background_color='white'):
|
||||||
|
img_temp = img
|
||||||
|
|
||||||
|
if img.mode != 'L':
|
||||||
|
img_temp = ImageOps.grayscale(img)
|
||||||
|
|
||||||
|
if background_color != 'white':
|
||||||
|
img_temp = ImageOps.invert(img)
|
||||||
|
|
||||||
|
img_mat = np.array(img)
|
||||||
|
|
||||||
|
power = 1
|
||||||
|
img_temp = ImageOps.autocontrast(img_temp, 1).filter(ImageFilter.BoxBlur(1))
|
||||||
|
img_temp = img_temp.point(lambda p: 255 if p <= threshold_from_power(power) else 0)
|
||||||
|
|
||||||
|
if direction in ["horizontal", "both"]:
|
||||||
|
rows_idx_to_remove = empty_sections(img_temp, keep, horizontal=True)
|
||||||
|
img_mat = np.delete(img_mat, rows_idx_to_remove, 0)
|
||||||
|
|
||||||
|
if direction in ["vertical", "both"]:
|
||||||
|
cols_idx_to_remove = empty_sections(img_temp, keep, horizontal=False)
|
||||||
|
img_mat = np.delete(img_mat, cols_idx_to_remove, 1)
|
||||||
|
|
||||||
|
return Image.fromarray(img_mat)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Finds empty sections (excluding near borders).
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
img (PIL image): A PIL image.
|
||||||
|
keep (float): Distance to keep between panels after cropping (in percentage relative to the original distance).
|
||||||
|
horizontal (boolean): True to find empty rows, False to find empty columns.
|
||||||
|
Returns:
|
||||||
|
Itertable (list or NumPy array): indices of rows or columns to remove.
|
||||||
|
'''
|
||||||
|
def empty_sections(img, keep, horizontal=True):
|
||||||
|
axis = 1 if horizontal else 0
|
||||||
|
|
||||||
|
img_mat = np.array(img)
|
||||||
|
img_mat_max = np.max(img_mat, axis=axis)
|
||||||
|
img_mat_empty_idx = np.where(img_mat_max == 0)[0]
|
||||||
|
|
||||||
|
empty_sections = group_close_values(img_mat_empty_idx, 1)
|
||||||
|
sections_to_remove = []
|
||||||
|
for section in empty_sections:
|
||||||
|
if section[1] < img.size[1] * 0.99 and section[0] > img.size[1] * 0.01: # if not near borders
|
||||||
|
sections_to_remove.append(section)
|
||||||
|
|
||||||
|
if len(sections_to_remove) != 0:
|
||||||
|
sections_to_remove_after_keep = [(int(x1+(keep/2)*(x2-x1)), int(x2-(keep/2)*(x2-x1))) for x1,x2 in sections_to_remove]
|
||||||
|
idx_to_remove = np.concatenate([np.arange(x1, x2) for x1,x2 in sections_to_remove_after_keep])
|
||||||
|
|
||||||
|
return idx_to_remove
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
from PIL import ImageOps, ImageFilter
|
from PIL import ImageOps, ImageFilter
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from .common_crop import threshold_from_power, group_close_values
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Some assupmptions on the page number sizes
|
Some assupmptions on the page number sizes
|
||||||
@@ -51,12 +53,11 @@ def get_bbox_crop_margin_page_number(img, power=1, background_color='white'):
|
|||||||
threshold = threshold_from_power(power)
|
threshold = threshold_from_power(power)
|
||||||
bw_img = img.point(lambda p: 255 if p <= threshold else 0)
|
bw_img = img.point(lambda p: 255 if p <= threshold else 0)
|
||||||
bw_bbox = bw_img.getbbox()
|
bw_bbox = bw_img.getbbox()
|
||||||
|
|
||||||
if not bw_bbox: # bbox cannot be found in case that the entire resulted image is black.
|
if not bw_bbox: # bbox cannot be found in case that the entire resulted image is black.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
left, top_y_pos, right, bot_y_pos = bw_bbox
|
left, top_y_pos, right, bot_y_pos = bw_bbox
|
||||||
|
|
||||||
'''
|
'''
|
||||||
We inspect the lower bottom part of the image where we suspect might be a page number.
|
We inspect the lower bottom part of the image where we suspect might be a page number.
|
||||||
We assume that page number consist of 1 to 3 digits and the total min and max size of the number
|
We assume that page number consist of 1 to 3 digits and the total min and max size of the number
|
||||||
@@ -73,7 +74,7 @@ def get_bbox_crop_margin_page_number(img, power=1, background_color='white'):
|
|||||||
img_part_mat = np.array(img_part)
|
img_part_mat = np.array(img_part)
|
||||||
window_groups = []
|
window_groups = []
|
||||||
for i in range(img_part.size[1]):
|
for i in range(img_part.size[1]):
|
||||||
row_groups = [(g[0], g[1], i, i) for g in group_pixels(img_part_mat[i], img.size[0]*max_dist_size[0], threshold)]
|
row_groups = [(g[0], g[1], i, i) for g in group_close_values(np.where(img_part_mat[i] <= threshold)[0], img.size[0]*max_dist_size[0])]
|
||||||
window_groups.extend(row_groups)
|
window_groups.extend(row_groups)
|
||||||
|
|
||||||
window_groups = np.array(window_groups)
|
window_groups = np.array(window_groups)
|
||||||
@@ -109,7 +110,6 @@ def get_bbox_crop_margin_page_number(img, power=1, background_color='white'):
|
|||||||
cropped_bbox = (0, 0, img.size[0], bot_y_pos-(window_h-boxes_in_same_y_range[0][2]+1))
|
cropped_bbox = (0, 0, img.size[0], bot_y_pos-(window_h-boxes_in_same_y_range[0][2]+1))
|
||||||
|
|
||||||
cropped_bbox = bw_img.crop(cropped_bbox).getbbox()
|
cropped_bbox = bw_img.crop(cropped_bbox).getbbox()
|
||||||
|
|
||||||
return cropped_bbox
|
return cropped_bbox
|
||||||
|
|
||||||
|
|
||||||
@@ -145,33 +145,6 @@ def get_bbox_crop_margin(img, power=1, background_color='white'):
|
|||||||
return bw_img.getbbox()
|
return bw_img.getbbox()
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
Groups close pixels together (x axis)
|
|
||||||
'''
|
|
||||||
def group_pixels(row, max_dist_tolerated, threshold):
|
|
||||||
groups = []
|
|
||||||
idx = np.where(row <= threshold)[0]
|
|
||||||
|
|
||||||
group_start = -1
|
|
||||||
group_end = 0
|
|
||||||
for i in range(len(idx)):
|
|
||||||
dist = idx[i] - group_end
|
|
||||||
if group_start == -1:
|
|
||||||
group_start = idx[i]
|
|
||||||
group_end = idx[i]
|
|
||||||
elif dist <= max_dist_tolerated:
|
|
||||||
group_end = idx[i]
|
|
||||||
else:
|
|
||||||
groups.append((group_start, group_end))
|
|
||||||
group_start = -1
|
|
||||||
group_end = -1
|
|
||||||
|
|
||||||
if group_start != -1:
|
|
||||||
groups.append((group_start, group_end))
|
|
||||||
|
|
||||||
return groups
|
|
||||||
|
|
||||||
|
|
||||||
def box_intersect(box1, box2, max_dist):
|
def box_intersect(box1, box2, max_dist):
|
||||||
return not (box2[0]-max_dist[0] > box1[1]
|
return not (box2[0]-max_dist[0] > box1[1]
|
||||||
or box2[1]+max_dist[0] < box1[0]
|
or box2[1]+max_dist[0] < box1[0]
|
||||||
@@ -209,7 +182,3 @@ def merge_boxes(boxes, max_dist_tolerated):
|
|||||||
else:
|
else:
|
||||||
j += 1
|
j += 1
|
||||||
return boxes
|
return boxes
|
||||||
|
|
||||||
|
|
||||||
def threshold_from_power(power):
|
|
||||||
return 240-(power*64)
|
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from functools import lru_cache
|
||||||
import os
|
import os
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
@@ -49,7 +50,7 @@ class HTMLStripper(HTMLParser):
|
|||||||
def getImageFileName(imgfile):
|
def getImageFileName(imgfile):
|
||||||
name, ext = os.path.splitext(imgfile)
|
name, ext = os.path.splitext(imgfile)
|
||||||
ext = ext.lower()
|
ext = ext.lower()
|
||||||
if (name.startswith('.') and len(name) == 1) or ext not in ['.png', '.jpg', '.jpeg', '.gif', '.webp']:
|
if (name.startswith('.') and len(name) == 1) or ext not in ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.jp2', '.j2k', '.jpx']:
|
||||||
return None
|
return None
|
||||||
return [name, ext]
|
return [name, ext]
|
||||||
|
|
||||||
@@ -74,16 +75,6 @@ def walkLevel(some_dir, level=1):
|
|||||||
del dirs[:]
|
del dirs[:]
|
||||||
|
|
||||||
|
|
||||||
def md5Checksum(fpath):
|
|
||||||
with open(fpath, 'rb') as fh:
|
|
||||||
m = md5()
|
|
||||||
while True:
|
|
||||||
data = fh.read(8192)
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
m.update(data)
|
|
||||||
return m.hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
def sanitizeTrace(traceback):
|
def sanitizeTrace(traceback):
|
||||||
return ''.join(format_tb(traceback))\
|
return ''.join(format_tb(traceback))\
|
||||||
@@ -137,6 +128,19 @@ def dependencyCheck(level):
|
|||||||
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def available_archive_tools():
|
||||||
|
available = []
|
||||||
|
|
||||||
|
for tool in ['tar', '7z', 'unar', 'unrar']:
|
||||||
|
try:
|
||||||
|
subprocess_run([tool], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
available.append(tool)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return available
|
||||||
|
|
||||||
def subprocess_run(command, **kwargs):
|
def subprocess_run(command, **kwargs):
|
||||||
if (os.name == 'nt'):
|
if (os.name == 'nt'):
|
||||||
kwargs.setdefault('creationflags', subprocess.CREATE_NO_WINDOW)
|
kwargs.setdefault('creationflags', subprocess.CREATE_NO_WINDOW)
|
||||||
|
|||||||
Reference in New Issue
Block a user