mirror of
https://github.com/ciromattia/kcc
synced 2026-04-18 23:19:00 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90c9ba7539 | ||
|
|
84da718167 | ||
|
|
fe7559e6a9 | ||
|
|
a79c740387 | ||
|
|
bc98eecae9 | ||
|
|
e7a07377ef | ||
|
|
07ef11013a | ||
|
|
551fe6edbf | ||
|
|
dbf8a3ddbd | ||
|
|
8f9e230b62 | ||
|
|
36d9a4151e | ||
|
|
3e88dabd1a | ||
|
|
3b7d949128 | ||
|
|
68186285bd | ||
|
|
0abf620698 | ||
|
|
69d3bf3278 | ||
|
|
793992f408 | ||
|
|
f41d5327e0 | ||
|
|
6f960aa1d0 | ||
|
|
17c0a73f9f | ||
|
|
1fa5a5b19b | ||
|
|
e8d05c16aa |
15
.github/FUNDING.yml
vendored
Normal file
15
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: eink_dude
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
|
polar: # Replace with a single Polar username
|
||||||
|
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||||
|
thanks_dev: # Replace with a single thanks.dev username
|
||||||
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
@@ -27,7 +27,7 @@ on underpowered ereaders with small storage capacities.
|
|||||||
KCC avoids many common formatting issues (some of which occur [even on the Kindle Store](https://github.com/ciromattia/kcc/wiki/Kindle-Store-bad-formatting)), such as:
|
KCC avoids many common formatting issues (some of which occur [even on the Kindle Store](https://github.com/ciromattia/kcc/wiki/Kindle-Store-bad-formatting)), such as:
|
||||||
1) faded black levels causing unneccessarily low contrast, which is hard to see and can cause eyestrain.
|
1) faded black levels causing unneccessarily low contrast, which is hard to see and can cause eyestrain.
|
||||||
2) unneccessary margins at the bottom of the screen
|
2) unneccessary margins at the bottom of the screen
|
||||||
3) Not utilizing the full 1860x2480 resolution of the 10" Kindle Scribe (feature in progress)
|
3) Not utilizing the full 1860x2480 resolution of the 10" Kindle Scribe
|
||||||
4) incorrect page turn direction for manga that's read right to left
|
4) incorrect page turn direction for manga that's read right to left
|
||||||
5) unaligned two page spreads in landscape, where pages are shifted over by 1
|
5) unaligned two page spreads in landscape, where pages are shifted over by 1
|
||||||
|
|
||||||
@@ -101,6 +101,8 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
|
|||||||
## FAQ
|
## FAQ
|
||||||
- All options have additional information in tooltips if you hover over the option.
|
- All options have additional information in tooltips if you hover over the option.
|
||||||
- To get the converted book onto your Kindle/Kobo, just drag and drop the mobi/kepub into the documents folder on your Kindle/Kobo via USB
|
- To get the converted book onto your Kindle/Kobo, just drag and drop the mobi/kepub into the documents folder on your Kindle/Kobo via USB
|
||||||
|
- Right to left mode not working?
|
||||||
|
- RTL mode only affects splitting order for CBZ output. Your cbz reader itself sets the page turn direction.
|
||||||
- Colors inverted?
|
- Colors inverted?
|
||||||
- Disable Kindle dark mode
|
- Disable Kindle dark mode
|
||||||
- Cannot connect Kindle Scribe or 2024+ Kindle to macOS
|
- Cannot connect Kindle Scribe or 2024+ Kindle to macOS
|
||||||
@@ -254,6 +256,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.
|
||||||
|
--rotatefirst Put rotated spread first in spread splitter option.
|
||||||
--reducerainbow Reduce rainbow effect on color eink by slightly blurring images
|
--reducerainbow Reduce rainbow effect on color eink by slightly blurring images
|
||||||
|
|
||||||
CUSTOM PROFILE:
|
CUSTOM PROFILE:
|
||||||
@@ -301,6 +304,8 @@ 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
|
An example PR adding a new checkbox is here: https://github.com/ciromattia/kcc/pull/785
|
||||||
|
|
||||||
|
video of adding a new checkbox: https://youtu.be/g3I8DU74C7g
|
||||||
|
|
||||||
Do not use `git merge` to merge master from upstream,
|
Do not use `git merge` to merge master from upstream,
|
||||||
use the "Sync fork" button on your fork on GitHub in your branch
|
use the "Sync fork" button on your fork on GitHub in your branch
|
||||||
to avoid weird looking merges in pull requests.
|
to avoid weird looking merges in pull requests.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ channels:
|
|||||||
- defaults
|
- defaults
|
||||||
dependencies:
|
dependencies:
|
||||||
- python=3.11
|
- python=3.11
|
||||||
- Pillow>=5.2.0
|
- Pillow>=11.3.0
|
||||||
- psutil>=5.9.5
|
- psutil>=5.9.5
|
||||||
- python-slugify>=1.2.1
|
- python-slugify>=1.2.1
|
||||||
- raven>=6.0.0
|
- raven>=6.0.0
|
||||||
|
|||||||
374
gui/KCC.ui
374
gui/KCC.ui
@@ -453,13 +453,13 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item row="4" column="2">
|
<item row="6" column="2">
|
||||||
<widget class="QCheckBox" name="croppingBox">
|
<widget class="QCheckBox" name="interPanelCropBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><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></string>
|
<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>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Cropping mode</string>
|
<string>Inter-panel crop</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="tristate">
|
<property name="tristate">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@@ -476,6 +476,41 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLineEdit" name="authorEdit">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="focusPolicy">
|
||||||
|
<enum>Qt::FocusPolicy::ClickFocus</enum>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Default Author is KCC</string>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Default Author</string>
|
||||||
|
</property>
|
||||||
|
<property name="clearButtonEnabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="2">
|
||||||
|
<widget class="QCheckBox" name="croppingBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><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></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Cropping mode</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QCheckBox" name="webtoonBox">
|
<widget class="QCheckBox" name="webtoonBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
@@ -486,6 +521,155 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
||||||
|
</widget>
|
||||||
|
</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="7" column="0">
|
||||||
|
<widget class="QCheckBox" name="comicinfoTitleBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Write Title from ComicInfo.xml</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>ComicInfo Title</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QCheckBox" name="qualityBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><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></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Panel View 4/2/HQ</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</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>Rainbow blur</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="6" column="0">
|
||||||
|
<widget class="QCheckBox" name="fileFusionBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>File Fusion</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</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="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="2">
|
||||||
|
<widget class="QCheckBox" name="disableProcessingBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<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 name="text">
|
||||||
|
<string>Disable processing</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<widget class="QCheckBox" name="chunkSizeCheckBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p><span style=" font-weight:700; text-decoration: underline;">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=" font-weight:700; text-decoration: underline;">Checked</span><br/>Output file size specified in &quot;Chunk size MB&quot; before split occurs.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Chunk size</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="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="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QCheckBox" name="rotateBox">
|
<widget class="QCheckBox" name="rotateBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
@@ -512,62 +696,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="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>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="2">
|
|
||||||
<widget class="QCheckBox" name="qualityBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><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></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Panel View 4/2/HQ</string>
|
|
||||||
</property>
|
|
||||||
<property name="tristate">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="2">
|
|
||||||
<widget class="QCheckBox" name="disableProcessingBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<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 name="text">
|
|
||||||
<string>Disable processing</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="QCheckBox" name="maximizeStrips">
|
<widget class="QCheckBox" name="maximizeStrips">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
@@ -578,131 +706,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0">
|
<item row="8" column="1">
|
||||||
<widget class="QLineEdit" name="authorEdit">
|
<widget class="QCheckBox" name="rotateFirstBox">
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="focusPolicy">
|
|
||||||
<enum>Qt::FocusPolicy::ClickFocus</enum>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Default Author is KCC</string>
|
<string><html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html></string>
|
||||||
</property>
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>Default Author</string>
|
|
||||||
</property>
|
|
||||||
<property name="clearButtonEnabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</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>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Delete input</string>
|
<string>Rotate First</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="6" column="0">
|
|
||||||
<widget class="QCheckBox" name="fileFusionBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>File Fusion</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>Rainbow blur</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="7" column="1">
|
|
||||||
<widget class="QCheckBox" name="chunkSizeCheckBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p><span style=" font-weight:700; text-decoration: underline;">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=" font-weight:700; text-decoration: underline;">Checked</span><br/>Output file size specified in &quot;Chunk size MB&quot; before split occurs.</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Chunk size</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="7" column="0">
|
|
||||||
<widget class="QCheckBox" name="comicinfoTitleBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Write Title from ComicInfo.xml</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>ComicInfo Title</string>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ 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, available_archive_tools, sanitizeTrace, walkLevel, subprocess_run
|
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run
|
||||||
|
from .comicarchive import SEVENZIP, available_archive_tools
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from . import comic2ebook
|
from . import comic2ebook
|
||||||
from . import image
|
|
||||||
from . import metadata
|
from . import metadata
|
||||||
from . import kindle
|
from . import kindle
|
||||||
from . import KCC_ui
|
from . import KCC_ui
|
||||||
@@ -278,6 +278,8 @@ class WorkerThread(QThread):
|
|||||||
options.filefusion = False
|
options.filefusion = False
|
||||||
if GUI.noRotateBox.isChecked():
|
if GUI.noRotateBox.isChecked():
|
||||||
options.norotate = True
|
options.norotate = True
|
||||||
|
if GUI.rotateFirstBox.isChecked():
|
||||||
|
options.rotatefirst = True
|
||||||
if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked:
|
if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||||
options.forcepng = True
|
options.forcepng = True
|
||||||
elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
|
elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
|
||||||
@@ -881,6 +883,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
'fileFusionBox': GUI.fileFusionBox.checkState().value,
|
'fileFusionBox': GUI.fileFusionBox.checkState().value,
|
||||||
'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState().value,
|
'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState().value,
|
||||||
'noRotateBox': GUI.noRotateBox.checkState().value,
|
'noRotateBox': GUI.noRotateBox.checkState().value,
|
||||||
|
'rotateFirstBox': GUI.rotateFirstBox.checkState().value,
|
||||||
'maximizeStrips': GUI.maximizeStrips.checkState().value,
|
'maximizeStrips': GUI.maximizeStrips.checkState().value,
|
||||||
'gammaSlider': float(self.gammaValue) * 100,
|
'gammaSlider': float(self.gammaValue) * 100,
|
||||||
'chunkSizeCheckBox': GUI.chunkSizeCheckBox.checkState().value,
|
'chunkSizeCheckBox': GUI.chunkSizeCheckBox.checkState().value,
|
||||||
@@ -1166,7 +1169,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
'info')
|
'info')
|
||||||
|
|
||||||
self.tar = 'tar' in available_archive_tools()
|
self.tar = 'tar' in available_archive_tools()
|
||||||
self.sevenzip = '7z' in available_archive_tools()
|
self.sevenzip = SEVENZIP in available_archive_tools()
|
||||||
if not any([self.tar, self.sevenzip]):
|
if not any([self.tar, self.sevenzip]):
|
||||||
self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
|
self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
|
||||||
' to enable CBZ/CBR/ZIP/etc processing.', 'warning')
|
' to enable CBZ/CBR/ZIP/etc processing.', 'warning')
|
||||||
|
|||||||
@@ -12228,7 +12228,7 @@ qt_resource_struct = b"\
|
|||||||
\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\x88;p\xbcJ\
|
\x00\x00\x01\x88;p\xbcJ\
|
||||||
\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\x97~\xfd]]\
|
\x00\x00\x01\x97\xc9|\x88\xde\
|
||||||
\x00\x00\x01V\x00\x00\x00\x00\x00\x01\x00\x01\x9d\x9a\
|
\x00\x00\x01V\x00\x00\x00\x00\x00\x01\x00\x01\x9d\x9a\
|
||||||
\x00\x00\x01\x88;p\xbcI\
|
\x00\x00\x01\x88;p\xbcI\
|
||||||
\x00\x00\x01\xb8\x00\x00\x00\x00\x00\x01\x00\x01\xf6n\
|
\x00\x00\x01\xb8\x00\x00\x00\x00\x00\x01\x00\x01\xf6n\
|
||||||
|
|||||||
@@ -251,65 +251,16 @@ 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.croppingBox = QCheckBox(self.optionWidget)
|
|
||||||
self.croppingBox.setObjectName(u"croppingBox")
|
|
||||||
self.croppingBox.setTristate(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
|
|
||||||
|
|
||||||
self.mangaBox = QCheckBox(self.optionWidget)
|
|
||||||
self.mangaBox.setObjectName(u"mangaBox")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
|
|
||||||
|
|
||||||
self.webtoonBox = QCheckBox(self.optionWidget)
|
|
||||||
self.webtoonBox.setObjectName(u"webtoonBox")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1)
|
|
||||||
|
|
||||||
self.rotateBox = QCheckBox(self.optionWidget)
|
|
||||||
self.rotateBox.setObjectName(u"rotateBox")
|
|
||||||
self.rotateBox.setTristate(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
|
|
||||||
|
|
||||||
self.borderBox = QCheckBox(self.optionWidget)
|
|
||||||
self.borderBox.setObjectName(u"borderBox")
|
|
||||||
self.borderBox.setTristate(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1)
|
|
||||||
|
|
||||||
self.gammaBox = QCheckBox(self.optionWidget)
|
|
||||||
self.gammaBox.setObjectName(u"gammaBox")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
|
|
||||||
|
|
||||||
self.interPanelCropBox = QCheckBox(self.optionWidget)
|
self.interPanelCropBox = QCheckBox(self.optionWidget)
|
||||||
self.interPanelCropBox.setObjectName(u"interPanelCropBox")
|
self.interPanelCropBox.setObjectName(u"interPanelCropBox")
|
||||||
self.interPanelCropBox.setTristate(True)
|
self.interPanelCropBox.setTristate(True)
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1)
|
||||||
|
|
||||||
self.colorBox = QCheckBox(self.optionWidget)
|
self.mangaBox = QCheckBox(self.optionWidget)
|
||||||
self.colorBox.setObjectName(u"colorBox")
|
self.mangaBox.setObjectName(u"mangaBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
|
||||||
|
|
||||||
self.qualityBox = QCheckBox(self.optionWidget)
|
|
||||||
self.qualityBox.setObjectName(u"qualityBox")
|
|
||||||
self.qualityBox.setTristate(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1)
|
|
||||||
|
|
||||||
self.disableProcessingBox = QCheckBox(self.optionWidget)
|
|
||||||
self.disableProcessingBox.setObjectName(u"disableProcessingBox")
|
|
||||||
|
|
||||||
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")
|
||||||
@@ -323,27 +274,79 @@ 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.croppingBox = QCheckBox(self.optionWidget)
|
||||||
|
self.croppingBox.setObjectName(u"croppingBox")
|
||||||
|
self.croppingBox.setTristate(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
|
||||||
|
|
||||||
|
self.webtoonBox = QCheckBox(self.optionWidget)
|
||||||
|
self.webtoonBox.setObjectName(u"webtoonBox")
|
||||||
|
|
||||||
|
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.deleteBox = QCheckBox(self.optionWidget)
|
self.deleteBox = QCheckBox(self.optionWidget)
|
||||||
self.deleteBox.setObjectName(u"deleteBox")
|
self.deleteBox.setObjectName(u"deleteBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
|
||||||
|
|
||||||
|
self.comicinfoTitleBox = QCheckBox(self.optionWidget)
|
||||||
|
self.comicinfoTitleBox.setObjectName(u"comicinfoTitleBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.comicinfoTitleBox, 7, 0, 1, 1)
|
||||||
|
|
||||||
|
self.qualityBox = QCheckBox(self.optionWidget)
|
||||||
|
self.qualityBox.setObjectName(u"qualityBox")
|
||||||
|
self.qualityBox.setTristate(True)
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1)
|
||||||
|
|
||||||
|
self.reduceRainbowBox = QCheckBox(self.optionWidget)
|
||||||
|
self.reduceRainbowBox.setObjectName(u"reduceRainbowBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.reduceRainbowBox, 7, 2, 1, 1)
|
||||||
|
|
||||||
|
self.noRotateBox = QCheckBox(self.optionWidget)
|
||||||
|
self.noRotateBox.setObjectName(u"noRotateBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
|
||||||
|
|
||||||
|
self.fileFusionBox = QCheckBox(self.optionWidget)
|
||||||
|
self.fileFusionBox.setObjectName(u"fileFusionBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1)
|
||||||
|
|
||||||
|
self.gammaBox = QCheckBox(self.optionWidget)
|
||||||
|
self.gammaBox.setObjectName(u"gammaBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
|
||||||
|
|
||||||
self.mozJpegBox = QCheckBox(self.optionWidget)
|
self.mozJpegBox = QCheckBox(self.optionWidget)
|
||||||
self.mozJpegBox.setObjectName(u"mozJpegBox")
|
self.mozJpegBox.setObjectName(u"mozJpegBox")
|
||||||
self.mozJpegBox.setTristate(True)
|
self.mozJpegBox.setTristate(True)
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1)
|
||||||
|
|
||||||
|
self.disableProcessingBox = QCheckBox(self.optionWidget)
|
||||||
|
self.disableProcessingBox.setObjectName(u"disableProcessingBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1)
|
||||||
|
|
||||||
|
self.chunkSizeCheckBox = QCheckBox(self.optionWidget)
|
||||||
|
self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1)
|
||||||
|
|
||||||
self.spreadShiftBox = QCheckBox(self.optionWidget)
|
self.spreadShiftBox = QCheckBox(self.optionWidget)
|
||||||
self.spreadShiftBox.setObjectName(u"spreadShiftBox")
|
self.spreadShiftBox.setObjectName(u"spreadShiftBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1)
|
||||||
|
|
||||||
self.fileFusionBox = QCheckBox(self.optionWidget)
|
|
||||||
self.fileFusionBox.setObjectName(u"fileFusionBox")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1)
|
|
||||||
|
|
||||||
self.upscaleBox = QCheckBox(self.optionWidget)
|
self.upscaleBox = QCheckBox(self.optionWidget)
|
||||||
self.upscaleBox.setObjectName(u"upscaleBox")
|
self.upscaleBox.setObjectName(u"upscaleBox")
|
||||||
self.upscaleBox.setTristate(True)
|
self.upscaleBox.setTristate(True)
|
||||||
@@ -355,25 +358,27 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
|
||||||
|
|
||||||
self.noRotateBox = QCheckBox(self.optionWidget)
|
self.rotateBox = QCheckBox(self.optionWidget)
|
||||||
self.noRotateBox.setObjectName(u"noRotateBox")
|
self.rotateBox.setObjectName(u"rotateBox")
|
||||||
|
self.rotateBox.setTristate(True)
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
|
||||||
|
|
||||||
self.reduceRainbowBox = QCheckBox(self.optionWidget)
|
self.borderBox = QCheckBox(self.optionWidget)
|
||||||
self.reduceRainbowBox.setObjectName(u"reduceRainbowBox")
|
self.borderBox.setObjectName(u"borderBox")
|
||||||
|
self.borderBox.setTristate(True)
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.reduceRainbowBox, 7, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1)
|
||||||
|
|
||||||
self.chunkSizeCheckBox = QCheckBox(self.optionWidget)
|
self.maximizeStrips = QCheckBox(self.optionWidget)
|
||||||
self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox")
|
self.maximizeStrips.setObjectName(u"maximizeStrips")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1)
|
||||||
|
|
||||||
self.comicinfoTitleBox = QCheckBox(self.optionWidget)
|
self.rotateFirstBox = QCheckBox(self.optionWidget)
|
||||||
self.comicinfoTitleBox.setObjectName(u"comicinfoTitleBox")
|
self.rotateFirstBox.setObjectName(u"rotateFirstBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.comicinfoTitleBox, 7, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
||||||
@@ -528,69 +533,73 @@ class Ui_mainWindow(object):
|
|||||||
self.preserveMarginLabel.setText(QCoreApplication.translate("mainWindow", u"Preserve Margin %", None))
|
self.preserveMarginLabel.setText(QCoreApplication.translate("mainWindow", u"Preserve Margin %", 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)
|
||||||
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))
|
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)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
|
self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", 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"Right-to-left mode", None))
|
self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left mode", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", 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)
|
#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))
|
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.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
|
self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
|
||||||
#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 - Split and rotate<br/></span>Double page spreads will be displayed twice. First split and then rotated. </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)
|
|
||||||
self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None))
|
|
||||||
#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))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", 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.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)
|
#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))
|
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)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None))
|
self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", 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.comicinfoTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"Write Title from ComicInfo.xml", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.comicinfoTitleBox.setText(QCoreApplication.translate("mainWindow", u"ComicInfo Title", 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><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html>", None))
|
self.reduceRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Reduce rainbow effect on color eink by slightly blurring images", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
|
self.reduceRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow blur", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#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))
|
self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
|
self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None))
|
self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None))
|
self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", 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.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.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None))
|
self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#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))
|
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)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None))
|
self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
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)
|
||||||
|
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.chunkSizeCheckBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:700; text-decoration: underline;\">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=\" font-weight:700; text-decoration: underline;\">Checked</span><br/>Output file size specified in "Chunk size MB" before split occurs.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#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))
|
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)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None))
|
self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None))
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
#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))
|
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)
|
#endif // QT_CONFIG(tooltip)
|
||||||
@@ -600,21 +609,21 @@ class Ui_mainWindow(object):
|
|||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
|
self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", 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.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 - Split and rotate<br/></span>Double page spreads will be displayed twice. First split and then rotated. </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.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None))
|
self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.reduceRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Reduce rainbow effect on color eink by slightly blurring images", 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.reduceRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow blur", None))
|
self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.chunkSizeCheckBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:700; text-decoration: underline;\">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=\" font-weight:700; text-decoration: underline;\">Checked</span><br/>Output file size specified in "Chunk size MB" before split occurs.</p></body></html>", None))
|
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)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None))
|
self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.comicinfoTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"Write Title from ComicInfo.xml", None))
|
self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.comicinfoTitleBox.setText(QCoreApplication.translate("mainWindow", u"ComicInfo Title", None))
|
self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None))
|
||||||
self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None))
|
self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Warning: chunk size greater than default may cause<br/>performance/battery issues, especially on older devices.</p></body></html>", None))
|
self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Warning: chunk size greater than default may cause<br/>performance/battery issues, especially on older devices.</p></body></html>", None))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '7.6.0'
|
__version__ = '8.0.4'
|
||||||
__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'
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ 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_sort_keygen
|
from natsort import os_sort_keygen, os_sorted
|
||||||
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 pathlib import Path
|
from pathlib import Path
|
||||||
@@ -42,7 +42,8 @@ from subprocess import STDOUT, PIPE, CalledProcessError
|
|||||||
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 available_archive_tools, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
from .shared import getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
||||||
|
from .comicarchive import SEVENZIP, available_archive_tools
|
||||||
from . import comic2panel
|
from . import comic2panel
|
||||||
from . import image
|
from . import image
|
||||||
from . import comicarchive
|
from . import comicarchive
|
||||||
@@ -638,21 +639,22 @@ def imgFileProcessing(work):
|
|||||||
workImg = image.ComicPageParser((dirpath, afile), opt)
|
workImg = image.ComicPageParser((dirpath, afile), opt)
|
||||||
for i in workImg.payload:
|
for i in workImg.payload:
|
||||||
img = image.ComicPage(opt, *i)
|
img = image.ComicPage(opt, *i)
|
||||||
|
is_not_color = not opt.forcecolor or not img.color
|
||||||
|
if is_not_color:
|
||||||
|
img.convertToGrayscale()
|
||||||
if opt.cropping == 2 and not opt.webtoon:
|
if opt.cropping == 2 and not opt.webtoon:
|
||||||
img.cropPageNumber(opt.croppingp, opt.croppingm)
|
img.cropPageNumber(opt.croppingp, opt.croppingm)
|
||||||
if opt.cropping == 1 and not opt.webtoon:
|
if opt.cropping == 1 and not opt.webtoon:
|
||||||
img.cropMargin(opt.croppingp, opt.croppingm)
|
img.cropMargin(opt.croppingp, opt.croppingm)
|
||||||
if opt.interpanelcrop > 0:
|
if opt.interpanelcrop > 0:
|
||||||
img.cropInterPanelEmptySections("horizontal" if opt.interpanelcrop == 1 else "both")
|
img.cropInterPanelEmptySections("horizontal" if opt.interpanelcrop == 1 else "both")
|
||||||
img.autocontrastImage()
|
img.gammaCorrectImage()
|
||||||
|
if is_not_color:
|
||||||
|
img.autocontrastImage()
|
||||||
img.resizeImage()
|
img.resizeImage()
|
||||||
img.optimizeForDisplay(opt.reducerainbow)
|
img.optimizeForDisplay(opt.reducerainbow)
|
||||||
if opt.forcecolor and workImg.color:
|
if is_not_color and opt.forcepng:
|
||||||
pass
|
|
||||||
elif opt.forcepng:
|
|
||||||
img.quantizeImage()
|
img.quantizeImage()
|
||||||
else:
|
|
||||||
img.convertToGrayscale()
|
|
||||||
output.append(img.saveToDir())
|
output.append(img.saveToDir())
|
||||||
return output
|
return output
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -690,16 +692,6 @@ def getWorkFolder(afile):
|
|||||||
cbx = comicarchive.ComicArchive(afile)
|
cbx = comicarchive.ComicArchive(afile)
|
||||||
path = cbx.extract(workdir)
|
path = cbx.extract(workdir)
|
||||||
sanitizePermissions(path)
|
sanitizePermissions(path)
|
||||||
tdir = os.listdir(workdir)
|
|
||||||
if len(tdir) == 2 and 'ComicInfo.xml' in tdir:
|
|
||||||
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)
|
||||||
@@ -822,29 +814,48 @@ def getPanelViewSize(deviceres, size):
|
|||||||
return str(int(x)), str(int(y))
|
return str(int(x)), str(int(y))
|
||||||
|
|
||||||
|
|
||||||
|
def removeNonImages(filetree):
|
||||||
|
# clean dot from original file
|
||||||
|
dot_clean(filetree)
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(filetree):
|
||||||
|
for name in files:
|
||||||
|
_, ext = getImageFileName(name)
|
||||||
|
if ext not in ('.png', '.jpg', '.jpeg', '.gif', '.webp', '.jp2', '.avif'):
|
||||||
|
if os.path.exists(os.path.join(root, name)):
|
||||||
|
os.remove(os.path.join(root, name))
|
||||||
|
# remove empty nested folders
|
||||||
|
for root, dirs, files in os.walk(filetree, False):
|
||||||
|
if not files and not dirs:
|
||||||
|
os.rmdir(root)
|
||||||
|
|
||||||
|
|
||||||
def sanitizeTree(filetree):
|
def sanitizeTree(filetree):
|
||||||
chapterNames = {}
|
chapterNames = {}
|
||||||
page = 1
|
page = 1
|
||||||
cover_path = None
|
cover_path = None
|
||||||
for root, dirs, files in os.walk(filetree):
|
for root, dirs, files in os.walk(filetree):
|
||||||
dirs.sort(key=OS_SORT_KEY)
|
|
||||||
files.sort(key=OS_SORT_KEY)
|
files.sort(key=OS_SORT_KEY)
|
||||||
for name in files:
|
for name in files:
|
||||||
splitname = os.path.splitext(name)
|
_, ext = getImageFileName(name)
|
||||||
|
|
||||||
# 9999 page limit
|
# 9999 page limit
|
||||||
slugified = f'kcc-{page:04}'
|
unique_name = f'kcc-{page:04}'
|
||||||
page += 1
|
page += 1
|
||||||
|
|
||||||
newKey = os.path.join(root, slugified + splitname[1])
|
newKey = os.path.join(root, unique_name + ext)
|
||||||
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)
|
||||||
if not cover_path:
|
if not cover_path:
|
||||||
cover_path = newKey
|
cover_path = newKey
|
||||||
|
is_natural_sorted = False
|
||||||
|
if os_sorted(dirs) == sorted(dirs):
|
||||||
|
is_natural_sorted = True
|
||||||
|
dirs.sort(key=OS_SORT_KEY)
|
||||||
for i, name in enumerate(dirs):
|
for i, name in enumerate(dirs):
|
||||||
tmpName = name
|
tmpName = name
|
||||||
slugified = slugify(name)
|
slugified = slugify(name, is_natural_sorted)
|
||||||
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():
|
||||||
slugified += "A"
|
slugified += "A"
|
||||||
chapterNames[slugified] = tmpName
|
chapterNames[slugified] = tmpName
|
||||||
@@ -870,8 +881,7 @@ def sanitizePermissions(filetree):
|
|||||||
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD)
|
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD)
|
||||||
for name in dirs:
|
for name in dirs:
|
||||||
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)
|
||||||
# clean dot from original file
|
|
||||||
dot_clean(filetree)
|
|
||||||
|
|
||||||
def dot_clean(filetree):
|
def dot_clean(filetree):
|
||||||
for root, _, files in os.walk(filetree, topdown=False):
|
for root, _, files in os.walk(filetree, topdown=False):
|
||||||
@@ -990,10 +1000,6 @@ def detectSuboptimalProcessing(tmppath, orgpath):
|
|||||||
os.remove(os.path.join(root, name))
|
os.remove(os.path.join(root, name))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise RuntimeError(f"{name}: {e}")
|
raise RuntimeError(f"{name}: {e}")
|
||||||
# remove empty nested folders
|
|
||||||
for root, dirs, files in os.walk(tmppath, False):
|
|
||||||
if not files and not dirs:
|
|
||||||
os.rmdir(root)
|
|
||||||
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:
|
||||||
@@ -1016,23 +1022,27 @@ def createNewTome(parent):
|
|||||||
return tomePath, tomePathRoot
|
return tomePath, tomePathRoot
|
||||||
|
|
||||||
|
|
||||||
def slugify(value):
|
def slugify(value, is_natural_sorted):
|
||||||
if options.format == 'CBZ':
|
if options.format == 'CBZ' and is_natural_sorted:
|
||||||
return value
|
return value
|
||||||
value = slugify_ext(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
|
if options.format != 'CBZ':
|
||||||
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
# convert all unicode to ascii via slugify
|
||||||
|
value = slugify_ext(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
|
||||||
|
if not is_natural_sorted:
|
||||||
|
# pad zeros to numbers
|
||||||
|
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def makeZIP(zipfilename, basedir, isepub=False):
|
def makeZIP(zipfilename, basedir, isepub=False):
|
||||||
start = perf_counter()
|
start = perf_counter()
|
||||||
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
||||||
if '7z' in available_archive_tools():
|
if SEVENZIP in available_archive_tools():
|
||||||
if isepub:
|
if isepub:
|
||||||
mimetypeFile = open(os.path.join(basedir, 'mimetype'), 'w')
|
mimetypeFile = open(os.path.join(basedir, 'mimetype'), 'w')
|
||||||
mimetypeFile.write('application/epub+zip')
|
mimetypeFile.write('application/epub+zip')
|
||||||
mimetypeFile.close()
|
mimetypeFile.close()
|
||||||
subprocess_run(['7z', 'a', '-tzip', zipfilename, os.path.join(basedir, "*")], capture_output=True, check=True)
|
subprocess_run([SEVENZIP, 'a', '-tzip', zipfilename, os.path.join(basedir, "*")], capture_output=True, check=True)
|
||||||
else:
|
else:
|
||||||
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
||||||
if isepub:
|
if isepub:
|
||||||
@@ -1097,6 +1107,8 @@ def makeParser():
|
|||||||
help="Shift first page to opposite side in landscape for spread alignment")
|
help="Shift first page to opposite side in landscape for spread alignment")
|
||||||
output_options.add_argument("--norotate", action="store_true", dest="norotate", default=False,
|
output_options.add_argument("--norotate", action="store_true", dest="norotate", default=False,
|
||||||
help="Do not rotate double page spreads in spread splitter option.")
|
help="Do not rotate double page spreads in spread splitter option.")
|
||||||
|
output_options.add_argument("--rotatefirst", action="store_true", dest="rotatefirst", default=False,
|
||||||
|
help="Put rotated 2 page spread first in spread splitter option.")
|
||||||
|
|
||||||
processing_options.add_argument("-n", "--noprocessing", action="store_true", dest="noprocessing", default=False,
|
processing_options.add_argument("-n", "--noprocessing", action="store_true", dest="noprocessing", default=False,
|
||||||
help="Do not modify image and ignore any profil or processing option")
|
help="Do not modify image and ignore any profil or processing option")
|
||||||
@@ -1232,7 +1244,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'):
|
||||||
if '7z' not in available_archive_tools():
|
if SEVENZIP not in available_archive_tools():
|
||||||
print('ERROR: 7z is missing!')
|
print('ERROR: 7z is missing!')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if options.format == 'MOBI':
|
if options.format == 'MOBI':
|
||||||
@@ -1309,6 +1321,7 @@ 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)
|
||||||
|
removeNonImages(os.path.join(path, "OEBPS", "Images"))
|
||||||
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
|
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
|
||||||
chapterNames, cover_path = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
chapterNames, cover_path = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||||
cover = image.Cover(cover_path, options)
|
cover = image.Cover(cover_path, options)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
from functools import cached_property
|
from functools import cached_property, lru_cache
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import distro
|
import distro
|
||||||
@@ -28,6 +28,7 @@ from xml.parsers.expat import ExpatError
|
|||||||
from .shared import subprocess_run
|
from .shared import subprocess_run
|
||||||
|
|
||||||
EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.'
|
EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.'
|
||||||
|
SEVENZIP = '7zz' if platform.system() == 'Darwin' else '7z'
|
||||||
|
|
||||||
|
|
||||||
class ComicArchive:
|
class ComicArchive:
|
||||||
@@ -39,7 +40,7 @@ class ComicArchive:
|
|||||||
@cached_property
|
@cached_property
|
||||||
def type(self):
|
def type(self):
|
||||||
extraction_commands = [
|
extraction_commands = [
|
||||||
['7z', 'l', '-y', '-p1', self.filepath],
|
[SEVENZIP, 'l', '-y', '-p1', self.filepath],
|
||||||
]
|
]
|
||||||
|
|
||||||
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
||||||
@@ -68,12 +69,12 @@ class ComicArchive:
|
|||||||
|
|
||||||
extraction_commands = [
|
extraction_commands = [
|
||||||
['tar', '--exclude', '__MACOSX', '--exclude', '.DS_Store', '--exclude', 'thumbs.db', '--exclude', 'Thumbs.db', '-xf', self.filepath, '-C', targetdir],
|
['tar', '--exclude', '__MACOSX', '--exclude', '.DS_Store', '--exclude', 'thumbs.db', '--exclude', 'Thumbs.db', '-xf', self.filepath, '-C', targetdir],
|
||||||
['7z', 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.filepath],
|
[SEVENZIP, 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.filepath],
|
||||||
]
|
]
|
||||||
|
|
||||||
if platform.system() == 'Darwin':
|
if platform.system() == 'Darwin':
|
||||||
extraction_commands.append(
|
extraction_commands.append(
|
||||||
['unar', self.filepath, '-f', '-o', targetdir]
|
['unar', self.filepath, '-D', '-f', '-o', targetdir]
|
||||||
)
|
)
|
||||||
|
|
||||||
extraction_commands.reverse()
|
extraction_commands.reverse()
|
||||||
@@ -100,13 +101,13 @@ class ComicArchive:
|
|||||||
def addFile(self, sourcefile):
|
def addFile(self, sourcefile):
|
||||||
if self.type in ['RAR', 'RAR5']:
|
if self.type in ['RAR', 'RAR5']:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
process = subprocess_run(['7z', 'a', '-y', self.filepath, sourcefile],
|
process = subprocess_run([SEVENZIP, 'a', '-y', self.filepath, sourcefile],
|
||||||
stdout=PIPE, stderr=STDOUT)
|
stdout=PIPE, stderr=STDOUT)
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
raise OSError('Failed to add the file.')
|
raise OSError('Failed to add the file.')
|
||||||
|
|
||||||
def extractMetadata(self):
|
def extractMetadata(self):
|
||||||
process = subprocess_run(['7z', 'x', '-y', '-so', self.filepath, 'ComicInfo.xml'],
|
process = subprocess_run([SEVENZIP, 'x', '-y', '-so', self.filepath, 'ComicInfo.xml'],
|
||||||
stdout=PIPE, stderr=STDOUT)
|
stdout=PIPE, stderr=STDOUT)
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
raise OSError(EXTRACTION_ERROR)
|
raise OSError(EXTRACTION_ERROR)
|
||||||
@@ -114,3 +115,16 @@ class ComicArchive:
|
|||||||
return parseString(process.stdout)
|
return parseString(process.stdout)
|
||||||
except ExpatError:
|
except ExpatError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def available_archive_tools():
|
||||||
|
available = []
|
||||||
|
|
||||||
|
for tool in ['tar', SEVENZIP, 'unar', 'unrar']:
|
||||||
|
try:
|
||||||
|
subprocess_run([tool], stdout=PIPE, stderr=STDOUT)
|
||||||
|
available.append(tool)
|
||||||
|
except (FileNotFoundError, CalledProcessError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return available
|
||||||
|
|||||||
@@ -20,7 +20,9 @@
|
|||||||
# 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
|
||||||
|
import numpy as np
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from functools import cached_property
|
||||||
import mozjpeg_lossless_optimization
|
import mozjpeg_lossless_optimization
|
||||||
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter, ImageDraw
|
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter, ImageDraw
|
||||||
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
|
||||||
@@ -146,11 +148,9 @@ class ComicPageParser:
|
|||||||
|
|
||||||
# Detect corruption in source image, let caller catch any exceptions triggered.
|
# Detect corruption in source image, let caller catch any exceptions triggered.
|
||||||
srcImgPath = os.path.join(source[0], source[1])
|
srcImgPath = os.path.join(source[0], source[1])
|
||||||
|
Image.open(srcImgPath).verify()
|
||||||
self.image = Image.open(srcImgPath)
|
self.image = Image.open(srcImgPath)
|
||||||
self.image.verify()
|
|
||||||
self.image = Image.open(srcImgPath).convert('RGB')
|
|
||||||
|
|
||||||
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
|
||||||
if not hasattr(Image, 'Resampling'):
|
if not hasattr(Image, 'Resampling'):
|
||||||
@@ -181,13 +181,13 @@ class ComicPageParser:
|
|||||||
new_image = Image.new("RGB", (int(width / 2), int(height*2)))
|
new_image = Image.new("RGB", (int(width / 2), int(height*2)))
|
||||||
new_image.paste(pageone, (0, 0))
|
new_image.paste(pageone, (0, 0))
|
||||||
new_image.paste(pagetwo, (0, height))
|
new_image.paste(pagetwo, (0, height))
|
||||||
self.payload.append(['N', self.source, new_image, self.color, self.fill])
|
self.payload.append(['N', self.source, new_image, self.fill])
|
||||||
elif (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \
|
elif (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \
|
||||||
and not self.opt.webtoon and self.opt.splitter == 1:
|
and not self.opt.webtoon and self.opt.splitter == 1:
|
||||||
spread = self.image
|
spread = self.image
|
||||||
if not self.opt.norotate:
|
if not self.opt.norotate:
|
||||||
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||||
self.payload.append(['R', self.source, spread, self.color, self.fill])
|
self.payload.append(['R', self.source, spread, self.fill])
|
||||||
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
|
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
|
||||||
if self.opt.splitter != 1:
|
if self.opt.splitter != 1:
|
||||||
if width > height:
|
if width > height:
|
||||||
@@ -202,38 +202,15 @@ class ComicPageParser:
|
|||||||
else:
|
else:
|
||||||
pageone = self.image.crop(leftbox)
|
pageone = self.image.crop(leftbox)
|
||||||
pagetwo = self.image.crop(rightbox)
|
pagetwo = self.image.crop(rightbox)
|
||||||
self.payload.append(['S1', self.source, pageone, self.color, self.fill])
|
self.payload.append(['S1', self.source, pageone, self.fill])
|
||||||
self.payload.append(['S2', self.source, pagetwo, self.color, self.fill])
|
self.payload.append(['S2', self.source, pagetwo, self.fill])
|
||||||
if self.opt.splitter > 0:
|
if self.opt.splitter > 0:
|
||||||
spread = self.image
|
spread = self.image
|
||||||
if not self.opt.norotate:
|
if not self.opt.norotate:
|
||||||
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||||
self.payload.append(['R', self.source, spread,
|
self.payload.append(['R', self.source, spread, self.fill])
|
||||||
self.color, self.fill])
|
|
||||||
else:
|
else:
|
||||||
self.payload.append(['N', self.source, self.image, self.color, self.fill])
|
self.payload.append(['N', self.source, self.image, self.fill])
|
||||||
|
|
||||||
def colorCheck(self):
|
|
||||||
if self.opt.webtoon:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
img = self.image.copy()
|
|
||||||
bands = img.getbands()
|
|
||||||
if bands == ('R', 'G', 'B') or bands == ('R', 'G', 'B', 'A'):
|
|
||||||
thumb = img.resize((40, 40))
|
|
||||||
SSE, bias = 0, [0, 0, 0]
|
|
||||||
bias = ImageStat.Stat(thumb).mean[:3]
|
|
||||||
bias = [b - sum(bias) / 3 for b in bias]
|
|
||||||
for pixel in thumb.getdata():
|
|
||||||
mu = sum(pixel) / 3
|
|
||||||
SSE += sum((pixel[i] - mu - bias[i]) * (pixel[i] - mu - bias[i]) for i in [0, 1, 2])
|
|
||||||
MSE = float(SSE) / (40 * 40)
|
|
||||||
if MSE > 22:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def fillCheck(self):
|
def fillCheck(self):
|
||||||
if self.opt.bordersColor:
|
if self.opt.bordersColor:
|
||||||
@@ -275,14 +252,14 @@ class ComicPageParser:
|
|||||||
|
|
||||||
|
|
||||||
class ComicPage:
|
class ComicPage:
|
||||||
def __init__(self, options, mode, path, image, color, fill):
|
def __init__(self, options, mode, path, image, fill):
|
||||||
self.opt = options
|
self.opt = options
|
||||||
_, self.size, self.palette, self.gamma = self.opt.profileData
|
_, self.size, self.palette, self.gamma = self.opt.profileData
|
||||||
if self.opt.hq:
|
if self.opt.hq:
|
||||||
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
|
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
|
||||||
self.kindle_scribe_azw3 = (options.profile == 'KS') and (options.format in ('MOBI', 'EPUB'))
|
self.kindle_scribe_azw3 = (options.profile == 'KS') and (options.format in ('MOBI', 'EPUB'))
|
||||||
self.image = image
|
self.original_color_mode = image.mode
|
||||||
self.color = color
|
self.image = image.convert("RGB")
|
||||||
self.fill = fill
|
self.fill = fill
|
||||||
self.rotated = False
|
self.rotated = False
|
||||||
self.orgPath = os.path.join(path[0], path[1])
|
self.orgPath = os.path.join(path[0], path[1])
|
||||||
@@ -290,7 +267,7 @@ class ComicPage:
|
|||||||
if 'N' in mode:
|
if 'N' in mode:
|
||||||
self.targetPathOrder = '-kcc-x'
|
self.targetPathOrder = '-kcc-x'
|
||||||
elif 'R' in mode:
|
elif 'R' in mode:
|
||||||
self.targetPathOrder = '-kcc-d'
|
self.targetPathOrder = '-kcc-a' if options.rotatefirst else '-kcc-d'
|
||||||
if not options.norotate:
|
if not options.norotate:
|
||||||
self.rotated = True
|
self.rotated = True
|
||||||
elif 'S1' in mode:
|
elif 'S1' in mode:
|
||||||
@@ -301,6 +278,26 @@ class ComicPage:
|
|||||||
if not hasattr(Image, 'Resampling'):
|
if not hasattr(Image, 'Resampling'):
|
||||||
Image.Resampling = Image
|
Image.Resampling = Image
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def color(self):
|
||||||
|
if self.original_color_mode in ("L", "1"):
|
||||||
|
return False
|
||||||
|
img = self.image.convert("YCbCr")
|
||||||
|
_, cb, cr = img.split()
|
||||||
|
|
||||||
|
cb_hist = cb.histogram()
|
||||||
|
cr_hist = cr.histogram()
|
||||||
|
cb_nonzero = [i for i, e in enumerate(cb_hist) if e]
|
||||||
|
cr_nonzero = [i for i, e in enumerate(cr_hist) if e]
|
||||||
|
cb_spread = cb_nonzero[-1] - cb_nonzero[0] if len(cb_nonzero) else 0
|
||||||
|
cr_spread = cr_nonzero[-1] - cr_nonzero[0] if len(cr_nonzero) else 0
|
||||||
|
|
||||||
|
SPREAD_THRESHOLD=20
|
||||||
|
if cb_spread < SPREAD_THRESHOLD and cr_spread < SPREAD_THRESHOLD:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
def saveToDir(self):
|
def saveToDir(self):
|
||||||
try:
|
try:
|
||||||
flags = []
|
flags = []
|
||||||
@@ -344,16 +341,21 @@ class ComicPage:
|
|||||||
image.save(targetPath, 'JPEG', optimize=1, quality=85)
|
image.save(targetPath, 'JPEG', optimize=1, quality=85)
|
||||||
return targetPath
|
return targetPath
|
||||||
|
|
||||||
def autocontrastImage(self):
|
def gammaCorrectImage(self):
|
||||||
gamma = self.opt.gamma
|
gamma = self.opt.gamma
|
||||||
if gamma < 0.1:
|
if gamma < 0.1:
|
||||||
gamma = self.gamma
|
gamma = self.gamma
|
||||||
if self.gamma != 1.0 and self.color:
|
if self.gamma != 1.0 and self.color:
|
||||||
gamma = 1.0
|
gamma = 1.0
|
||||||
if gamma == 1.0:
|
if gamma == 1.0:
|
||||||
self.image = ImageOps.autocontrast(self.image)
|
pass
|
||||||
else:
|
else:
|
||||||
self.image = ImageOps.autocontrast(Image.eval(self.image, lambda a: int(255 * (a / 255.) ** gamma)))
|
self.image = Image.eval(self.image, lambda a: int(255 * (a / 255.) ** gamma))
|
||||||
|
|
||||||
|
def autocontrastImage(self):
|
||||||
|
# autocontrast on non grayscale images has unexpected results
|
||||||
|
# since it autocontrasts each color channel separately
|
||||||
|
self.image = ImageOps.autocontrast(self.image)
|
||||||
|
|
||||||
def convertToGrayscale(self):
|
def convertToGrayscale(self):
|
||||||
self.image = self.image.convert('L')
|
self.image = self.image.convert('L')
|
||||||
@@ -361,7 +363,7 @@ class ComicPage:
|
|||||||
def quantizeImage(self):
|
def quantizeImage(self):
|
||||||
# remove all color pixels from image, since colorCheck() has some tolerance
|
# remove all color pixels from image, since colorCheck() has some tolerance
|
||||||
# quantize with a small number of color pixels in a mostly b/w image can have unexpected results
|
# quantize with a small number of color pixels in a mostly b/w image can have unexpected results
|
||||||
self.image = self.image.convert("L").convert("RGB")
|
self.image = self.image.convert("RGB")
|
||||||
|
|
||||||
palImg = Image.new('P', (1, 1))
|
palImg = Image.new('P', (1, 1))
|
||||||
palImg.putpalette(self.palette)
|
palImg.putpalette(self.palette)
|
||||||
@@ -476,6 +478,9 @@ class Cover:
|
|||||||
stroke_width=25
|
stroke_width=25
|
||||||
)
|
)
|
||||||
copy.save(target, "JPEG", optimize=1, quality=85)
|
copy.save(target, "JPEG", optimize=1, quality=85)
|
||||||
|
dot_cover = Path(target).with_stem('._' + Path(target).stem)
|
||||||
|
if os.path.exists(dot_cover):
|
||||||
|
os.remove(dot_cover)
|
||||||
except IOError:
|
except IOError:
|
||||||
raise RuntimeError('Failed to save cover.')
|
raise RuntimeError('Failed to save cover.')
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
from functools import lru_cache
|
|
||||||
import os
|
import os
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -49,12 +48,6 @@ 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):
|
|
||||||
return None
|
|
||||||
if name.startswith('._'):
|
|
||||||
return None
|
|
||||||
if ext not in ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.jp2', '.j2k', '.jpx']:
|
|
||||||
return None
|
|
||||||
return [name, ext]
|
return [name, ext]
|
||||||
|
|
||||||
|
|
||||||
@@ -123,27 +116,14 @@ def dependencyCheck(level):
|
|||||||
missing.append('python-slugify 1.2.1+')
|
missing.append('python-slugify 1.2.1+')
|
||||||
try:
|
try:
|
||||||
from PIL import __version__ as pillowVersion
|
from PIL import __version__ as pillowVersion
|
||||||
if Version('5.2.0') > Version(pillowVersion):
|
if Version('11.3.0') > Version(pillowVersion):
|
||||||
missing.append('Pillow 5.2.0+')
|
missing.append('Pillow 11.3.0+')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
missing.append('Pillow 5.2.0+')
|
missing.append('Pillow 11.3.0+')
|
||||||
if len(missing) > 0:
|
if len(missing) > 0:
|
||||||
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, subprocess.CalledProcessError):
|
|
||||||
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)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
PySide6>=6.5.1
|
PySide6>=6.5.1
|
||||||
Pillow>=5.2.0
|
Pillow>=11.3.0
|
||||||
psutil>=5.9.5
|
psutil>=5.9.5
|
||||||
requests>=2.31.0
|
requests>=2.31.0
|
||||||
python-slugify>=1.2.1
|
python-slugify>=1.2.1
|
||||||
raven>=6.0.0
|
raven>=6.0.0
|
||||||
packaging>=23.2
|
packaging>=23.2
|
||||||
mozjpeg-lossless-optimization==1.2.0
|
mozjpeg-lossless-optimization>=1.2.0
|
||||||
natsort>=8.4.0
|
natsort>=8.4.0
|
||||||
distro>=1.8.0
|
distro>=1.8.0
|
||||||
numpy>=1.22.4
|
numpy>=1.22.4
|
||||||
|
|||||||
4
setup.py
4
setup.py
@@ -75,7 +75,7 @@ setuptools.setup(
|
|||||||
packages=['kindlecomicconverter'],
|
packages=['kindlecomicconverter'],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'pyside6>=6.5.1',
|
'pyside6>=6.5.1',
|
||||||
'Pillow>=5.2.0',
|
'Pillow>=11.3.0',
|
||||||
'psutil>=5.9.5',
|
'psutil>=5.9.5',
|
||||||
'python-slugify>=1.2.1,<9.0.0',
|
'python-slugify>=1.2.1,<9.0.0',
|
||||||
'raven>=6.0.0',
|
'raven>=6.0.0',
|
||||||
@@ -83,7 +83,7 @@ setuptools.setup(
|
|||||||
'mozjpeg-lossless-optimization>=1.1.2',
|
'mozjpeg-lossless-optimization>=1.1.2',
|
||||||
'natsort>=8.4.0',
|
'natsort>=8.4.0',
|
||||||
'distro',
|
'distro',
|
||||||
'numpy>=1.22.4,<2.0.0'
|
'numpy>=1.22.4'
|
||||||
],
|
],
|
||||||
classifiers=[],
|
classifiers=[],
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
|||||||
Reference in New Issue
Block a user