mirror of
https://github.com/ciromattia/kcc
synced 2026-04-18 15:08:48 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3a3e9b3c2 | ||
|
|
380dc5c42c | ||
|
|
8ab7520754 | ||
|
|
3cd6e09bcb | ||
|
|
cb5f4db5c4 | ||
|
|
f1ffb2c4e8 | ||
|
|
26327728d0 | ||
|
|
61bfb0a51f | ||
|
|
2f03119926 | ||
|
|
8f08c63f4e | ||
|
|
eb24a400b4 | ||
|
|
cc2eb9dcf3 | ||
|
|
d1a443b3d8 | ||
|
|
f8c35ce634 | ||
|
|
580a800d25 | ||
|
|
023797f012 | ||
|
|
63a18627d3 | ||
|
|
0d967084a0 | ||
|
|
2da5b11858 | ||
|
|
f6d10337d8 | ||
|
|
cf047ecf6f | ||
|
|
e7ee8bed9d | ||
|
|
9c8a1759cf | ||
|
|
6299754964 | ||
|
|
a3db86a29b | ||
|
|
67714a9b06 | ||
|
|
95f9a3cda9 | ||
|
|
0e12fc30c6 |
@@ -99,6 +99,12 @@ On Mac, right click open to get past the security warning.
|
|||||||
For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation
|
For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
- Should I use Calibre?
|
||||||
|
- No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output in Calibre will break the formatting.
|
||||||
|
Viewing KCC output in Calibre will also not work properly.
|
||||||
|
On 7th gen and later Kindles running firmware 5.16.3+, you can get cover thumbnails simply by USB dropping into documents folder.
|
||||||
|
On 6th gen and older, you can get cover thumbnails by keeping Kindle plugged in during conversion.
|
||||||
|
If you are careful to not modify the file however, you can still use Calibre, but direct USB dropping is reccomended.
|
||||||
- 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?
|
- Right to left mode not working?
|
||||||
@@ -224,6 +230,7 @@ PROCESSING:
|
|||||||
Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]
|
Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]
|
||||||
-g GAMMA, --gamma GAMMA
|
-g GAMMA, --gamma GAMMA
|
||||||
Apply gamma correction to linearize the image [Default=Auto]
|
Apply gamma correction to linearize the image [Default=Auto]
|
||||||
|
--autolevel Set most common dark pixel value to be black point for leveling.
|
||||||
-c CROPPING, --cropping CROPPING
|
-c CROPPING, --cropping CROPPING
|
||||||
Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]
|
Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]
|
||||||
--cp CROPPINGP, --croppingpower CROPPINGP
|
--cp CROPPINGP, --croppingpower CROPPINGP
|
||||||
@@ -257,7 +264,7 @@ OUTPUT SETTINGS:
|
|||||||
--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.
|
--rotatefirst Put rotated spread first in spread splitter option.
|
||||||
--reducerainbow Reduce rainbow effect on color eink by slightly blurring images
|
--eraserainbow Erase rainbow effect on color eink screen by attenuating interfering frequencies
|
||||||
|
|
||||||
CUSTOM PROFILE:
|
CUSTOM PROFILE:
|
||||||
--customwidth CUSTOMWIDTH
|
--customwidth CUSTOMWIDTH
|
||||||
|
|||||||
274
gui/KCC.ui
274
gui/KCC.ui
@@ -453,6 +453,39 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
<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="1" column="0">
|
||||||
|
<widget class="QCheckBox" name="mangaBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Right-to-left mode</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QCheckBox" name="borderBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><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></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>W/B margins</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="6" column="2">
|
<item row="6" column="2">
|
||||||
<widget class="QCheckBox" name="interPanelCropBox">
|
<widget class="QCheckBox" name="interPanelCropBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
@@ -466,13 +499,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="6" column="0">
|
||||||
<widget class="QCheckBox" name="mangaBox">
|
<widget class="QCheckBox" name="fileFusionBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html></string>
|
<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>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Right-to-left mode</string>
|
<string>File Fusion</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -498,46 +531,56 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="2">
|
<item row="8" column="1">
|
||||||
<widget class="QCheckBox" name="croppingBox">
|
<widget class="QCheckBox" name="rotateFirstBox">
|
||||||
<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>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>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Cropping mode</string>
|
<string>Rotate First</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="2">
|
||||||
|
<widget class="QCheckBox" name="eraseRainbowBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Erase rainbow effect on color eink screen by attenuating interfering frequencies</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Rainbow eraser</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="1" column="1">
|
||||||
|
<widget class="QCheckBox" name="rotateBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><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></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Spread splitter</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="tristate">
|
<property name="tristate">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="3" column="1">
|
||||||
<widget class="QCheckBox" name="webtoonBox">
|
<widget class="QCheckBox" name="outputSplit">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html></string>
|
<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>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Webtoon mode</string>
|
<string>Output split</string>
|
||||||
</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="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>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -564,56 +607,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="2">
|
<item row="5" column="0">
|
||||||
<widget class="QCheckBox" name="reduceRainbowBox">
|
<widget class="QCheckBox" name="spreadShiftBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Reduce rainbow effect on color eink by slightly blurring images</string>
|
<string>Shift first page to opposite side in landscape for two page spread alignment</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Rainbow blur</string>
|
<string>Spread shift</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>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -627,69 +627,33 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="7" column="1">
|
<item row="2" column="0">
|
||||||
<widget class="QCheckBox" name="chunkSizeCheckBox">
|
<widget class="QCheckBox" name="webtoonBox">
|
||||||
<property name="toolTip">
|
<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>
|
<string><html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Chunk size</string>
|
<string>Webtoon mode</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<item row="3" column="2">
|
||||||
<widget class="QCheckBox" name="spreadShiftBox">
|
<widget class="QCheckBox" name="colorBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Shift first page to opposite side in landscape for two page spread alignment</string>
|
<string><html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Spread shift</string>
|
<string>Color mode</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="4" column="2">
|
||||||
<widget class="QCheckBox" name="upscaleBox">
|
<widget class="QCheckBox" name="croppingBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html></string>
|
<string><html><head/><body><p><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>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Stretch/Upscale</string>
|
<string>Cropping mode</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">
|
|
||||||
<widget class="QCheckBox" name="rotateBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><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></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Spread splitter</string>
|
|
||||||
</property>
|
|
||||||
<property name="tristate">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QCheckBox" name="borderBox">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><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></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>W/B margins</string>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="tristate">
|
<property name="tristate">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@@ -706,13 +670,59 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="1">
|
<item row="6" column="1">
|
||||||
<widget class="QCheckBox" name="rotateFirstBox">
|
<widget class="QCheckBox" name="noRotateBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<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>
|
<string>Do not rotate double page spreads in spread splitter option.</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Rotate First</string>
|
<string>No rotate</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="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="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="8" column="2">
|
||||||
|
<widget class="QCheckBox" name="autoLevelBox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Set the most common dark pixel value to be the black point for leveling on a page by page basis.</p><p>Skipped for any images that were originally color.</p><p>Use only if default autocontrast still results in very gray faded blacks. </p><p>Reccomended to use with Custom Gamma = 1.0 (Disabled).</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Aggressive Black Point</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -863,7 +873,7 @@
|
|||||||
<tabstop>chunkSizeBox</tabstop>
|
<tabstop>chunkSizeBox</tabstop>
|
||||||
<tabstop>noRotateBox</tabstop>
|
<tabstop>noRotateBox</tabstop>
|
||||||
<tabstop>interPanelCropBox</tabstop>
|
<tabstop>interPanelCropBox</tabstop>
|
||||||
<tabstop>reduceRainbowBox</tabstop>
|
<tabstop>eraseRainbowBox</tabstop>
|
||||||
<tabstop>heightBox</tabstop>
|
<tabstop>heightBox</tabstop>
|
||||||
<tabstop>croppingPowerSlider</tabstop>
|
<tabstop>croppingPowerSlider</tabstop>
|
||||||
<tabstop>editorButton</tabstop>
|
<tabstop>editorButton</tabstop>
|
||||||
|
|||||||
@@ -247,6 +247,8 @@ class WorkerThread(QThread):
|
|||||||
options.upscale = True
|
options.upscale = True
|
||||||
if GUI.gammaBox.isChecked() and float(GUI.gammaValue) > 0.09:
|
if GUI.gammaBox.isChecked() and float(GUI.gammaValue) > 0.09:
|
||||||
options.gamma = float(GUI.gammaValue)
|
options.gamma = float(GUI.gammaValue)
|
||||||
|
if GUI.autoLevelBox.isChecked():
|
||||||
|
options.autolevel = True
|
||||||
options.cropping = GUI.croppingBox.checkState().value
|
options.cropping = GUI.croppingBox.checkState().value
|
||||||
if GUI.croppingBox.checkState() != Qt.CheckState.Unchecked:
|
if GUI.croppingBox.checkState() != Qt.CheckState.Unchecked:
|
||||||
options.croppingp = float(GUI.croppingPowerValue)
|
options.croppingp = float(GUI.croppingPowerValue)
|
||||||
@@ -260,8 +262,8 @@ class WorkerThread(QThread):
|
|||||||
options.batchsplit = 2
|
options.batchsplit = 2
|
||||||
if GUI.colorBox.isChecked():
|
if GUI.colorBox.isChecked():
|
||||||
options.forcecolor = True
|
options.forcecolor = True
|
||||||
if GUI.reduceRainbowBox.isChecked():
|
if GUI.eraseRainbowBox.isChecked():
|
||||||
options.reducerainbow = True
|
options.eraserainbow = True
|
||||||
if GUI.maximizeStrips.isChecked():
|
if GUI.maximizeStrips.isChecked():
|
||||||
options.maximizestrips = True
|
options.maximizestrips = True
|
||||||
if GUI.disableProcessingBox.isChecked():
|
if GUI.disableProcessingBox.isChecked():
|
||||||
@@ -863,6 +865,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
'rotateBox': GUI.rotateBox.checkState().value,
|
'rotateBox': GUI.rotateBox.checkState().value,
|
||||||
'qualityBox': GUI.qualityBox.checkState().value,
|
'qualityBox': GUI.qualityBox.checkState().value,
|
||||||
'gammaBox': GUI.gammaBox.checkState().value,
|
'gammaBox': GUI.gammaBox.checkState().value,
|
||||||
|
'autoLevelBox': GUI.autoLevelBox.checkState().value,
|
||||||
'croppingBox': GUI.croppingBox.checkState().value,
|
'croppingBox': GUI.croppingBox.checkState().value,
|
||||||
'croppingPowerSlider': float(self.croppingPowerValue) * 100,
|
'croppingPowerSlider': float(self.croppingPowerValue) * 100,
|
||||||
'preserveMarginBox': self.preserveMarginBox.value(),
|
'preserveMarginBox': self.preserveMarginBox.value(),
|
||||||
@@ -872,7 +875,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
'webtoonBox': GUI.webtoonBox.checkState().value,
|
'webtoonBox': GUI.webtoonBox.checkState().value,
|
||||||
'outputSplit': GUI.outputSplit.checkState().value,
|
'outputSplit': GUI.outputSplit.checkState().value,
|
||||||
'colorBox': GUI.colorBox.checkState().value,
|
'colorBox': GUI.colorBox.checkState().value,
|
||||||
'reduceRainbowBox': GUI.reduceRainbowBox.checkState().value,
|
'eraseRainbowBox': GUI.eraseRainbowBox.checkState().value,
|
||||||
'disableProcessingBox': GUI.disableProcessingBox.checkState().value,
|
'disableProcessingBox': GUI.disableProcessingBox.checkState().value,
|
||||||
'comicinfoTitleBox': GUI.comicinfoTitleBox.checkState().value,
|
'comicinfoTitleBox': GUI.comicinfoTitleBox.checkState().value,
|
||||||
'mozJpegBox': GUI.mozJpegBox.checkState().value,
|
'mozJpegBox': GUI.mozJpegBox.checkState().value,
|
||||||
|
|||||||
@@ -251,16 +251,32 @@ 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.gammaBox = QCheckBox(self.optionWidget)
|
||||||
|
self.gammaBox.setObjectName(u"gammaBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
|
||||||
|
|
||||||
|
self.mangaBox = QCheckBox(self.optionWidget)
|
||||||
|
self.mangaBox.setObjectName(u"mangaBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 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.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.mangaBox = QCheckBox(self.optionWidget)
|
self.fileFusionBox = QCheckBox(self.optionWidget)
|
||||||
self.mangaBox.setObjectName(u"mangaBox")
|
self.fileFusionBox.setObjectName(u"fileFusionBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1)
|
||||||
|
|
||||||
self.authorEdit = QLineEdit(self.optionWidget)
|
self.authorEdit = QLineEdit(self.optionWidget)
|
||||||
self.authorEdit.setObjectName(u"authorEdit")
|
self.authorEdit.setObjectName(u"authorEdit")
|
||||||
@@ -274,26 +290,31 @@ 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.rotateFirstBox = QCheckBox(self.optionWidget)
|
||||||
self.croppingBox.setObjectName(u"croppingBox")
|
self.rotateFirstBox.setObjectName(u"rotateFirstBox")
|
||||||
self.croppingBox.setTristate(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1)
|
||||||
|
|
||||||
self.webtoonBox = QCheckBox(self.optionWidget)
|
self.eraseRainbowBox = QCheckBox(self.optionWidget)
|
||||||
self.webtoonBox.setObjectName(u"webtoonBox")
|
self.eraseRainbowBox.setObjectName(u"eraseRainbowBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1)
|
||||||
|
|
||||||
self.colorBox = QCheckBox(self.optionWidget)
|
self.chunkSizeCheckBox = QCheckBox(self.optionWidget)
|
||||||
self.colorBox.setObjectName(u"colorBox")
|
self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1)
|
||||||
|
|
||||||
self.deleteBox = QCheckBox(self.optionWidget)
|
self.rotateBox = QCheckBox(self.optionWidget)
|
||||||
self.deleteBox.setObjectName(u"deleteBox")
|
self.rotateBox.setObjectName(u"rotateBox")
|
||||||
|
self.rotateBox.setTristate(True)
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
|
||||||
|
|
||||||
|
self.outputSplit = QCheckBox(self.optionWidget)
|
||||||
|
self.outputSplit.setObjectName(u"outputSplit")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
|
||||||
|
|
||||||
self.comicinfoTitleBox = QCheckBox(self.optionWidget)
|
self.comicinfoTitleBox = QCheckBox(self.optionWidget)
|
||||||
self.comicinfoTitleBox.setObjectName(u"comicinfoTitleBox")
|
self.comicinfoTitleBox.setObjectName(u"comicinfoTitleBox")
|
||||||
@@ -306,46 +327,46 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1)
|
||||||
|
|
||||||
self.reduceRainbowBox = QCheckBox(self.optionWidget)
|
self.spreadShiftBox = QCheckBox(self.optionWidget)
|
||||||
self.reduceRainbowBox.setObjectName(u"reduceRainbowBox")
|
self.spreadShiftBox.setObjectName(u"spreadShiftBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.reduceRainbowBox, 7, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 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.setObjectName(u"mozJpegBox")
|
|
||||||
self.mozJpegBox.setTristate(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1)
|
|
||||||
|
|
||||||
self.disableProcessingBox = QCheckBox(self.optionWidget)
|
self.disableProcessingBox = QCheckBox(self.optionWidget)
|
||||||
self.disableProcessingBox.setObjectName(u"disableProcessingBox")
|
self.disableProcessingBox.setObjectName(u"disableProcessingBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1)
|
||||||
|
|
||||||
self.chunkSizeCheckBox = QCheckBox(self.optionWidget)
|
self.webtoonBox = QCheckBox(self.optionWidget)
|
||||||
self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox")
|
self.webtoonBox.setObjectName(u"webtoonBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1)
|
||||||
|
|
||||||
self.spreadShiftBox = QCheckBox(self.optionWidget)
|
self.colorBox = QCheckBox(self.optionWidget)
|
||||||
self.spreadShiftBox.setObjectName(u"spreadShiftBox")
|
self.colorBox.setObjectName(u"colorBox")
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1)
|
self.gridLayout_2.addWidget(self.colorBox, 3, 2, 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.maximizeStrips = QCheckBox(self.optionWidget)
|
||||||
|
self.maximizeStrips.setObjectName(u"maximizeStrips")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1)
|
||||||
|
|
||||||
|
self.noRotateBox = QCheckBox(self.optionWidget)
|
||||||
|
self.noRotateBox.setObjectName(u"noRotateBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
|
||||||
|
|
||||||
|
self.deleteBox = QCheckBox(self.optionWidget)
|
||||||
|
self.deleteBox.setObjectName(u"deleteBox")
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
|
||||||
|
|
||||||
self.upscaleBox = QCheckBox(self.optionWidget)
|
self.upscaleBox = QCheckBox(self.optionWidget)
|
||||||
self.upscaleBox.setObjectName(u"upscaleBox")
|
self.upscaleBox.setObjectName(u"upscaleBox")
|
||||||
@@ -353,32 +374,16 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1)
|
||||||
|
|
||||||
self.outputSplit = QCheckBox(self.optionWidget)
|
self.mozJpegBox = QCheckBox(self.optionWidget)
|
||||||
self.outputSplit.setObjectName(u"outputSplit")
|
self.mozJpegBox.setObjectName(u"mozJpegBox")
|
||||||
|
self.mozJpegBox.setTristate(True)
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.mozJpegBox, 4, 0, 1, 1)
|
||||||
|
|
||||||
self.rotateBox = QCheckBox(self.optionWidget)
|
self.autoLevelBox = QCheckBox(self.optionWidget)
|
||||||
self.rotateBox.setObjectName(u"rotateBox")
|
self.autoLevelBox.setObjectName(u"autoLevelBox")
|
||||||
self.rotateBox.setTristate(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.autoLevelBox, 8, 2, 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.maximizeStrips = QCheckBox(self.optionWidget)
|
|
||||||
self.maximizeStrips.setObjectName(u"maximizeStrips")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1)
|
|
||||||
|
|
||||||
self.rotateFirstBox = QCheckBox(self.optionWidget)
|
|
||||||
self.rotateFirstBox.setObjectName(u"rotateFirstBox")
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -468,8 +473,8 @@ class Ui_mainWindow(object):
|
|||||||
QWidget.setTabOrder(self.disableProcessingBox, self.chunkSizeBox)
|
QWidget.setTabOrder(self.disableProcessingBox, self.chunkSizeBox)
|
||||||
QWidget.setTabOrder(self.chunkSizeBox, self.noRotateBox)
|
QWidget.setTabOrder(self.chunkSizeBox, self.noRotateBox)
|
||||||
QWidget.setTabOrder(self.noRotateBox, self.interPanelCropBox)
|
QWidget.setTabOrder(self.noRotateBox, self.interPanelCropBox)
|
||||||
QWidget.setTabOrder(self.interPanelCropBox, self.reduceRainbowBox)
|
QWidget.setTabOrder(self.interPanelCropBox, self.eraseRainbowBox)
|
||||||
QWidget.setTabOrder(self.reduceRainbowBox, self.heightBox)
|
QWidget.setTabOrder(self.eraseRainbowBox, self.heightBox)
|
||||||
QWidget.setTabOrder(self.heightBox, self.croppingPowerSlider)
|
QWidget.setTabOrder(self.heightBox, self.croppingPowerSlider)
|
||||||
QWidget.setTabOrder(self.croppingPowerSlider, self.editorButton)
|
QWidget.setTabOrder(self.croppingPowerSlider, self.editorButton)
|
||||||
QWidget.setTabOrder(self.editorButton, self.wikiButton)
|
QWidget.setTabOrder(self.editorButton, self.wikiButton)
|
||||||
@@ -533,33 +538,49 @@ 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.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))
|
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.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None))
|
self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", 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.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.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled<br/></span>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.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.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None))
|
self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None))
|
self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#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.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.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
|
self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", 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.eraseRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Erase rainbow effect on color eink screen by attenuating interfering frequencies", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
|
self.eraseRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow eraser", 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.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)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None))
|
self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", 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.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.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None))
|
self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.comicinfoTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"Write Title from ComicInfo.xml", None))
|
self.comicinfoTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"Write Title from ComicInfo.xml", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
@@ -569,61 +590,49 @@ class Ui_mainWindow(object):
|
|||||||
#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.reduceRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Reduce rainbow effect on color eink by slightly blurring images", 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.reduceRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow blur", None))
|
self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None))
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.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)
|
|
||||||
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.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
#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.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
|
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#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.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.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None))
|
self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", 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.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.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None))
|
self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", 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.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Margins<br/></span>Margins</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None))
|
self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.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)
|
#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.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.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
|
self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html>", None))
|
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.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None))
|
self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", 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.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.autoLevelBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Set the most common dark pixel value to be the black point for leveling on a page by page basis.</p><p>Skipped for any images that were originally color.</p><p>Use only if default autocontrast still results in very gray faded blacks. </p><p>Reccomended to use with Custom Gamma = 1.0 (Disabled).</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.autoLevelBox.setText(QCoreApplication.translate("mainWindow", u"Aggressive Black Point", 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__ = '8.0.4'
|
__version__ = '9.0.0'
|
||||||
__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'
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from typing import List
|
|||||||
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
|
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||||
from tempfile import mkdtemp, gettempdir, TemporaryFile
|
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, cpu_count
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from natsort import os_sort_keygen, os_sorted
|
from natsort import os_sort_keygen, os_sorted
|
||||||
from slugify import slugify as slugify_ext
|
from slugify import slugify as slugify_ext
|
||||||
@@ -41,13 +41,14 @@ from pathlib import Path
|
|||||||
from subprocess import STDOUT, PIPE, CalledProcessError
|
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
|
||||||
|
import pymupdf
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
from .shared import getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
from .shared import getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
||||||
from .comicarchive import SEVENZIP, available_archive_tools
|
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
|
||||||
from . import pdfjpgextract
|
|
||||||
from . import dualmetafix
|
from . import dualmetafix
|
||||||
from . import metadata
|
from . import metadata
|
||||||
from . import kindle
|
from . import kindle
|
||||||
@@ -639,28 +640,162 @@ 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
|
is_color = (opt.forcecolor and 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.gammaCorrectImage()
|
img.gammaCorrectImage()
|
||||||
if is_not_color:
|
|
||||||
img.autocontrastImage()
|
img.autocontrastImage()
|
||||||
img.resizeImage()
|
img.resizeImage()
|
||||||
img.optimizeForDisplay(opt.reducerainbow)
|
img.optimizeForDisplay(opt.eraserainbow, is_color)
|
||||||
if is_not_color and opt.forcepng:
|
|
||||||
|
if is_color:
|
||||||
|
pass
|
||||||
|
elif opt.forcepng:
|
||||||
|
img.convertToGrayscale()
|
||||||
img.quantizeImage()
|
img.quantizeImage()
|
||||||
|
else:
|
||||||
|
img.convertToGrayscale()
|
||||||
output.append(img.saveToDir())
|
output.append(img.saveToDir())
|
||||||
return output
|
return output
|
||||||
except Exception:
|
except Exception:
|
||||||
return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
|
return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
|
||||||
|
|
||||||
|
|
||||||
|
def render_page(vector):
|
||||||
|
"""Render a page range of a document.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The PyMuPDF document cannot be part of the argument, because that
|
||||||
|
cannot be pickled. So we are being passed in just its filename.
|
||||||
|
This is no performance issue, because we are a separate process and
|
||||||
|
need to open the document anyway.
|
||||||
|
Any page-specific function can be processed here - rendering is just
|
||||||
|
an example - text extraction might be another.
|
||||||
|
The work must however be self-contained: no inter-process communication
|
||||||
|
or synchronization is possible with this design.
|
||||||
|
Care must also be taken with which parameters are contained in the
|
||||||
|
argument, because it will be passed in via pickling by the Pool class.
|
||||||
|
So any large objects will increase the overall duration.
|
||||||
|
Args:
|
||||||
|
vector: a list containing required parameters.
|
||||||
|
"""
|
||||||
|
# recreate the arguments
|
||||||
|
idx = vector[0] # this is the segment number we have to process
|
||||||
|
cpu = vector[1] # number of CPUs
|
||||||
|
filename = vector[2] # document filename
|
||||||
|
output_dir = vector[3]
|
||||||
|
target_height = vector[4]
|
||||||
|
with pymupdf.open(filename) as doc: # open the document
|
||||||
|
num_pages = doc.page_count # get number of pages
|
||||||
|
|
||||||
|
# pages per segment: make sure that cpu * seg_size >= num_pages!
|
||||||
|
seg_size = int(num_pages / cpu + 1)
|
||||||
|
seg_from = idx * seg_size # our first page number
|
||||||
|
seg_to = min(seg_from + seg_size, num_pages) # last page number
|
||||||
|
|
||||||
|
for i in range(seg_from, seg_to): # work through our page segment
|
||||||
|
page = doc[i]
|
||||||
|
zoom = target_height / page.rect.height
|
||||||
|
mat = pymupdf.Matrix(zoom, zoom)
|
||||||
|
# TODO: decide colorspace earlier so later color check is cheaper.
|
||||||
|
pix = page.get_pixmap(matrix=mat, colorspace='RGB', alpha=False)
|
||||||
|
pix.save(os.path.join(output_dir, "p-%i.png" % i))
|
||||||
|
print("Processed page numbers %i through %i" % (seg_from, seg_to - 1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def extract_page(vector):
|
||||||
|
"""For pages with single image (and no text). Otherwise it's recommended to use render_page()
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The PyMuPDF document cannot be part of the argument, because that
|
||||||
|
cannot be pickled. So we are being passed in just its filename.
|
||||||
|
This is no performance issue, because we are a separate process and
|
||||||
|
need to open the document anyway.
|
||||||
|
Any page-specific function can be processed here - rendering is just
|
||||||
|
an example - text extraction might be another.
|
||||||
|
The work must however be self-contained: no inter-process communication
|
||||||
|
or synchronization is possible with this design.
|
||||||
|
Care must also be taken with which parameters are contained in the
|
||||||
|
argument, because it will be passed in via pickling by the Pool class.
|
||||||
|
So any large objects will increase the overall duration.
|
||||||
|
Args:
|
||||||
|
vector: a list containing required parameters.
|
||||||
|
"""
|
||||||
|
# recreate the arguments
|
||||||
|
idx = vector[0] # this is the segment number we have to process
|
||||||
|
cpu = vector[1] # number of CPUs
|
||||||
|
filename = vector[2] # document filename
|
||||||
|
output_dir = vector[3]
|
||||||
|
|
||||||
|
|
||||||
|
with pymupdf.open(filename) as doc: # open the document
|
||||||
|
num_pages = doc.page_count # get number of pages
|
||||||
|
|
||||||
|
# pages per segment: make sure that cpu * seg_size >= num_pages!
|
||||||
|
seg_size = int(num_pages / cpu + 1)
|
||||||
|
seg_from = idx * seg_size # our first page number
|
||||||
|
seg_to = min(seg_from + seg_size, num_pages) # last page number
|
||||||
|
|
||||||
|
for i in range(seg_from, seg_to): # work through our page segment
|
||||||
|
output_path = os.path.join(output_dir, "p-%i.png" % i)
|
||||||
|
page = doc.load_page(i)
|
||||||
|
image_list = page.get_images()
|
||||||
|
if len(image_list) > 1:
|
||||||
|
raise UserWarning("mupdf_pdf_extract_page_image() function can be used only with single image pages.")
|
||||||
|
if not image_list:
|
||||||
|
width, height = int(page.rect.width), int(page.rect.height)
|
||||||
|
blank_page = Image.new("RGB", (width, height), "white")
|
||||||
|
blank_page.save(output_path)
|
||||||
|
xref = image_list[0][0]
|
||||||
|
d = doc.extract_image(xref)
|
||||||
|
if d['cs-name'] == 'DeviceCMYK':
|
||||||
|
pix = pymupdf.Pixmap(doc, xref)
|
||||||
|
pix = pymupdf.Pixmap(pymupdf.csRGB, pix)
|
||||||
|
pix.save(output_path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
with open(Path(output_path).with_suffix('.' + d['ext']), "wb") as imgout:
|
||||||
|
imgout.write(d["image"])
|
||||||
|
print("Processed page numbers %i through %i" % (seg_from, seg_to - 1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height):
|
||||||
|
render = False
|
||||||
|
with pymupdf.open(filename) as doc:
|
||||||
|
for page in doc:
|
||||||
|
page_text = page.get_text().strip()
|
||||||
|
if page_text != "":
|
||||||
|
render = True
|
||||||
|
break
|
||||||
|
if len(page.get_images()) > 1:
|
||||||
|
render = True
|
||||||
|
break
|
||||||
|
|
||||||
|
cpu = cpu_count()
|
||||||
|
|
||||||
|
# make vectors of arguments for the processes
|
||||||
|
vectors = [(i, cpu, filename, output_dir, target_height) for i in range(cpu)]
|
||||||
|
print("Starting %i processes for '%s'." % (cpu, filename))
|
||||||
|
|
||||||
|
|
||||||
|
start = perf_counter()
|
||||||
|
with Pool() as pool:
|
||||||
|
results = pool.map(
|
||||||
|
render_page if render else extract_page, vectors
|
||||||
|
)
|
||||||
|
end = perf_counter()
|
||||||
|
print(f"MuPDF: {end - start} sec")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getWorkFolder(afile):
|
def getWorkFolder(afile):
|
||||||
if os.path.isdir(afile):
|
if os.path.isdir(afile):
|
||||||
if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
|
if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
|
||||||
@@ -679,19 +814,37 @@ def getWorkFolder(afile):
|
|||||||
if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5:
|
if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5:
|
||||||
raise UserWarning("Not enough disk space to perform conversion.")
|
raise UserWarning("Not enough disk space to perform conversion.")
|
||||||
if afile.lower().endswith('.pdf'):
|
if afile.lower().endswith('.pdf'):
|
||||||
pdf = pdfjpgextract.PdfJpgExtract(afile)
|
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
||||||
path, njpg = pdf.extract()
|
path = workdir
|
||||||
workdir = path
|
|
||||||
sanitizePermissions(path)
|
sanitizePermissions(path)
|
||||||
if njpg == 0:
|
target_height = options.profileData[1][1]
|
||||||
|
if options.cropping == 1:
|
||||||
|
target_height = target_height + target_height*0.20 #Account for possible margin at the top and bottom
|
||||||
|
elif options.cropping == 2:
|
||||||
|
target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number
|
||||||
|
try:
|
||||||
|
mupdf_pdf_process_pages_parallel(afile, workdir, target_height)
|
||||||
|
except Exception as e:
|
||||||
rmtree(path, True)
|
rmtree(path, True)
|
||||||
raise UserWarning("Failed to extract images from PDF file.")
|
raise UserWarning(f"Failed to extract images from PDF file. {e}")
|
||||||
else:
|
else:
|
||||||
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
||||||
try:
|
try:
|
||||||
cbx = comicarchive.ComicArchive(afile)
|
cbx = comicarchive.ComicArchive(afile)
|
||||||
path = cbx.extract(workdir)
|
path = cbx.extract(workdir)
|
||||||
sanitizePermissions(path)
|
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)
|
||||||
@@ -828,6 +981,9 @@ def removeNonImages(filetree):
|
|||||||
for root, dirs, files in os.walk(filetree, False):
|
for root, dirs, files in os.walk(filetree, False):
|
||||||
if not files and not dirs:
|
if not files and not dirs:
|
||||||
os.rmdir(root)
|
os.rmdir(root)
|
||||||
|
|
||||||
|
if not os.listdir(Path(filetree).parent):
|
||||||
|
raise UserWarning('No images detected, nested archives are not supported.')
|
||||||
|
|
||||||
|
|
||||||
def sanitizeTree(filetree):
|
def sanitizeTree(filetree):
|
||||||
@@ -881,6 +1037,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)
|
||||||
|
dot_clean(filetree)
|
||||||
|
|
||||||
|
|
||||||
def dot_clean(filetree):
|
def dot_clean(filetree):
|
||||||
@@ -1120,6 +1277,8 @@ def makeParser():
|
|||||||
help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]")
|
help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]")
|
||||||
processing_options.add_argument("-g", "--gamma", type=float, dest="gamma", default="0.0",
|
processing_options.add_argument("-g", "--gamma", type=float, dest="gamma", default="0.0",
|
||||||
help="Apply gamma correction to linearize the image [Default=Auto]")
|
help="Apply gamma correction to linearize the image [Default=Auto]")
|
||||||
|
output_options.add_argument("--autolevel", action="store_true", dest="autolevel", default=False,
|
||||||
|
help="Set most common dark pixel value to be black point for leveling.")
|
||||||
processing_options.add_argument("-c", "--cropping", type=int, dest="cropping", default="2",
|
processing_options.add_argument("-c", "--cropping", type=int, dest="cropping", default="2",
|
||||||
help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
|
help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
|
||||||
processing_options.add_argument("--cp", "--croppingpower", type=float, dest="croppingp", default="1.0",
|
processing_options.add_argument("--cp", "--croppingpower", type=float, dest="croppingp", default="1.0",
|
||||||
@@ -1136,8 +1295,8 @@ def makeParser():
|
|||||||
help="Disable autodetection and force white borders")
|
help="Disable autodetection and force white borders")
|
||||||
processing_options.add_argument("--forcecolor", action="store_true", dest="forcecolor", default=False,
|
processing_options.add_argument("--forcecolor", action="store_true", dest="forcecolor", default=False,
|
||||||
help="Don't convert images to grayscale")
|
help="Don't convert images to grayscale")
|
||||||
output_options.add_argument("--reducerainbow", action="store_true", dest="reducerainbow", default=False,
|
output_options.add_argument("--eraserainbow", action="store_true", dest="eraserainbow", default=False,
|
||||||
help="Reduce rainbow effect on color eink by slightly blurring images.")
|
help="Erase rainbow effect on color eink screen by attenuating interfering frequencies")
|
||||||
processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False,
|
processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False,
|
||||||
help="Create PNG files instead JPEG")
|
help="Create PNG files instead JPEG")
|
||||||
processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
|
processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ from pathlib import Path
|
|||||||
from functools import cached_property
|
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 .rainbow_artifacts_eraser import erase_rainbow_artifacts
|
||||||
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
|
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
|
||||||
from .inter_panel_crop_alg import crop_empty_inter_panel
|
from .inter_panel_crop_alg import crop_empty_inter_panel
|
||||||
|
|
||||||
@@ -353,9 +355,19 @@ class ComicPage:
|
|||||||
self.image = 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):
|
def autocontrastImage(self):
|
||||||
# autocontrast on non grayscale images has unexpected results
|
if self.opt.autolevel and not self.color:
|
||||||
# since it autocontrasts each color channel separately
|
self.convertToGrayscale()
|
||||||
self.image = ImageOps.autocontrast(self.image)
|
h = self.image.histogram()
|
||||||
|
most_common_dark_pixel_count = max(h[:64])
|
||||||
|
black_point = h.index(most_common_dark_pixel_count)
|
||||||
|
bp = black_point
|
||||||
|
self.image = self.image.point(lambda p: p if p > bp else bp)
|
||||||
|
|
||||||
|
# don't autocontrast grayscale pages that were originally color
|
||||||
|
if not self.opt.forcecolor and self.color:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
|
||||||
|
|
||||||
def convertToGrayscale(self):
|
def convertToGrayscale(self):
|
||||||
self.image = self.image.convert('L')
|
self.image = self.image.convert('L')
|
||||||
@@ -369,13 +381,10 @@ class ComicPage:
|
|||||||
palImg.putpalette(self.palette)
|
palImg.putpalette(self.palette)
|
||||||
self.image = self.image.quantize(palette=palImg)
|
self.image = self.image.quantize(palette=palImg)
|
||||||
|
|
||||||
def optimizeForDisplay(self, reducerainbow):
|
def optimizeForDisplay(self, eraserainbow, is_color):
|
||||||
# Reduce rainbow artifacts for grayscale images by breaking up dither patterns that cause Moire interference with color filter array
|
# Erase rainbow artifacts for grayscale and color images by removing spectral frequencies that cause Moire interference with color filter array
|
||||||
if reducerainbow and not self.color:
|
if eraserainbow:
|
||||||
unsharpFilter = ImageFilter.UnsharpMask(radius=1, percent=100)
|
self.image = erase_rainbow_artifacts(self.image, is_color)
|
||||||
self.image = self.image.filter(unsharpFilter)
|
|
||||||
self.image = self.image.filter(ImageFilter.BoxBlur(1.0))
|
|
||||||
self.image = self.image.filter(unsharpFilter)
|
|
||||||
|
|
||||||
def resizeImage(self):
|
def resizeImage(self):
|
||||||
ratio_device = float(self.size[1]) / float(self.size[0])
|
ratio_device = float(self.size[1]) / float(self.size[0])
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
|
|
||||||
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
|
|
||||||
#
|
|
||||||
# Based upon the code snippet by Ned Batchelder
|
|
||||||
# (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html)
|
|
||||||
#
|
|
||||||
# Permission to use, copy, modify, and/or distribute this software for
|
|
||||||
# any purpose with or without fee is hereby granted, provided that the
|
|
||||||
# above copyright notice and this permission notice appear in all
|
|
||||||
# copies.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
||||||
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
||||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
||||||
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
||||||
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
|
||||||
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
||||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
# PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
#
|
|
||||||
|
|
||||||
import os
|
|
||||||
from random import choice
|
|
||||||
from string import ascii_uppercase, digits
|
|
||||||
|
|
||||||
# skip stray images a few pixels in size in some PDFs
|
|
||||||
# typical images are many thousands in length
|
|
||||||
# https://github.com/ciromattia/kcc/pull/546
|
|
||||||
STRAY_IMAGE_LENGTH_THRESHOLD = 300
|
|
||||||
|
|
||||||
|
|
||||||
class PdfJpgExtract:
|
|
||||||
def __init__(self, fname):
|
|
||||||
self.fname = fname
|
|
||||||
self.filename = os.path.splitext(fname)
|
|
||||||
self.path = self.filename[0] + "-KCC-" + ''.join(choice(ascii_uppercase + digits) for _ in range(3))
|
|
||||||
|
|
||||||
def getPath(self):
|
|
||||||
return self.path
|
|
||||||
|
|
||||||
def extract(self):
|
|
||||||
pdf = open(self.fname, "rb").read()
|
|
||||||
startmark = b"\xff\xd8"
|
|
||||||
startfix = 0
|
|
||||||
endmark = b"\xff\xd9"
|
|
||||||
endfix = 2
|
|
||||||
i = 0
|
|
||||||
njpg = 0
|
|
||||||
os.makedirs(self.path)
|
|
||||||
while True:
|
|
||||||
istream = pdf.find(b"stream", i)
|
|
||||||
if istream < 0:
|
|
||||||
break
|
|
||||||
istart = pdf.find(startmark, istream, istream + 20)
|
|
||||||
if istart < 0:
|
|
||||||
i = istream + 20
|
|
||||||
continue
|
|
||||||
iend = pdf.find(b"endstream", istart)
|
|
||||||
if iend < 0:
|
|
||||||
raise Exception("Didn't find end of stream!")
|
|
||||||
iend = pdf.find(endmark, iend - 20)
|
|
||||||
if iend < 0:
|
|
||||||
raise Exception("Didn't find end of JPG!")
|
|
||||||
istart += startfix
|
|
||||||
iend += endfix
|
|
||||||
i = iend
|
|
||||||
|
|
||||||
if iend - istart < STRAY_IMAGE_LENGTH_THRESHOLD:
|
|
||||||
continue
|
|
||||||
|
|
||||||
jpg = pdf[istart:iend]
|
|
||||||
jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb")
|
|
||||||
jpgfile.write(jpg)
|
|
||||||
jpgfile.close()
|
|
||||||
njpg += 1
|
|
||||||
|
|
||||||
return self.path, njpg
|
|
||||||
246
kindlecomicconverter/rainbow_artifacts_eraser.py
Normal file
246
kindlecomicconverter/rainbow_artifacts_eraser.py
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
def fourier_transform_image(img):
|
||||||
|
"""
|
||||||
|
Memory-optimized version that modifies the array in place when possible.
|
||||||
|
"""
|
||||||
|
# Convert with minimal copy
|
||||||
|
img_array = np.asarray(img, dtype=np.float32)
|
||||||
|
|
||||||
|
# Use rfft2 if the image is real to save memory
|
||||||
|
# and computation time (approximately 2x faster)
|
||||||
|
fft_result = np.fft.rfft2(img_array)
|
||||||
|
|
||||||
|
return fft_result
|
||||||
|
|
||||||
|
def attenuate_diagonal_frequencies(fft_spectrum, freq_threshold=0.30, target_angle=135,
|
||||||
|
angle_tolerance=10, attenuation_factor=0.10):
|
||||||
|
"""
|
||||||
|
Attenuates specific frequencies in the Fourier domain (optimized version for rfft2).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fft_spectrum: Result of 2D real Fourier transform (from rfft2)
|
||||||
|
freq_threshold: Frequency threshold in cycles/pixel (default: 0.3, theoretical max: 0.5)
|
||||||
|
target_angle: Target angle in degrees (default: 135)
|
||||||
|
angle_tolerance: Angular tolerance in degrees (default: 15)
|
||||||
|
attenuation_factor: Attenuation factor (0.1 = 90% attenuation)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
np.ndarray: Modified FFT with applied attenuation (same format as input)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get dimensions of the rfft2 result
|
||||||
|
if fft_spectrum.ndim == 2:
|
||||||
|
height, width_rfft = fft_spectrum.shape
|
||||||
|
else: # 3D array (color channels)
|
||||||
|
height, width_rfft = fft_spectrum.shape[:2]
|
||||||
|
|
||||||
|
# For rfft2, the original width is (width_rfft - 1) * 2
|
||||||
|
width_original = (width_rfft - 1) * 2
|
||||||
|
|
||||||
|
# Create frequency grids for rfft2 format
|
||||||
|
freq_y = np.fft.fftfreq(height, d=1.0)
|
||||||
|
freq_x = np.fft.rfftfreq(width_original, d=1.0) # Use rfftfreq for the X dimension
|
||||||
|
|
||||||
|
|
||||||
|
# Use broadcasting to create grids without meshgrid (more efficient)
|
||||||
|
freq_y_grid = freq_y.reshape(-1, 1) # Column
|
||||||
|
freq_x_grid = freq_x.reshape(1, -1) # Row
|
||||||
|
|
||||||
|
# Calculate squared radial frequencies (avoid sqrt)
|
||||||
|
freq_radial_sq = freq_x_grid**2 + freq_y_grid**2
|
||||||
|
freq_threshold_sq = freq_threshold**2
|
||||||
|
|
||||||
|
# Frequency condition
|
||||||
|
freq_condition = freq_radial_sq >= freq_threshold_sq
|
||||||
|
|
||||||
|
# Early exit if no frequency satisfies the condition
|
||||||
|
if not np.any(freq_condition):
|
||||||
|
return fft_spectrum
|
||||||
|
|
||||||
|
# Calculate angles only where necessary
|
||||||
|
# Use atan2 directly with broadcasting
|
||||||
|
angles_rad = np.arctan2(freq_y_grid, freq_x_grid)
|
||||||
|
|
||||||
|
# Convert to degrees and normalize in a single operation
|
||||||
|
angles_deg = np.rad2deg(angles_rad) % 360
|
||||||
|
|
||||||
|
# Calculation of complementary angle
|
||||||
|
target_angle_2 = (target_angle + 180) % 360
|
||||||
|
|
||||||
|
# Calulation of perpendicular angles (135° + 45° to maximize compatibility until we know for sure which angle configure for each device)
|
||||||
|
target_angle_3 = (target_angle + 90) % 360
|
||||||
|
target_angle_4 = (target_angle_3 + 180) % 360
|
||||||
|
|
||||||
|
# Create angular conditions in a vectorized way
|
||||||
|
angle_condition = np.zeros_like(angles_deg, dtype=bool)
|
||||||
|
|
||||||
|
# Process both angles simultaneously
|
||||||
|
for angle in [target_angle, target_angle_2, target_angle_3, target_angle_4]:
|
||||||
|
min_angle = (angle - angle_tolerance) % 360
|
||||||
|
max_angle = (angle + angle_tolerance) % 360
|
||||||
|
|
||||||
|
if min_angle > max_angle: # Interval crosses 0°
|
||||||
|
angle_condition |= (angles_deg >= min_angle) | (angles_deg <= max_angle)
|
||||||
|
else: # Normal interval
|
||||||
|
angle_condition |= (angles_deg >= min_angle) & (angles_deg <= max_angle)
|
||||||
|
|
||||||
|
# Combine conditions
|
||||||
|
combined_condition = freq_condition & angle_condition
|
||||||
|
|
||||||
|
# Apply attenuation directly (avoid creating a full mask)
|
||||||
|
if attenuation_factor == 0:
|
||||||
|
# Special case: complete suppression
|
||||||
|
if fft_spectrum.ndim == 2:
|
||||||
|
fft_spectrum[combined_condition] = 0
|
||||||
|
else: # 3D array
|
||||||
|
fft_spectrum[combined_condition, :] = 0
|
||||||
|
return fft_spectrum
|
||||||
|
elif attenuation_factor == 1:
|
||||||
|
# Special case: no attenuation
|
||||||
|
return fft_spectrum
|
||||||
|
else:
|
||||||
|
# General case: partial attenuation
|
||||||
|
if fft_spectrum.ndim == 2:
|
||||||
|
fft_spectrum[combined_condition] *= attenuation_factor
|
||||||
|
else: # 3D array
|
||||||
|
fft_spectrum[combined_condition, :] *= attenuation_factor
|
||||||
|
return fft_spectrum
|
||||||
|
|
||||||
|
def inverse_fourier_transform_image(fft_spectrum, is_color, original_shape=None):
|
||||||
|
"""
|
||||||
|
Performs an optimized inverse Fourier transform to reconstruct a PIL image.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fft_spectrum: Fourier transform result (complex array from rfft2)
|
||||||
|
is_color: Boolean indicating if the image is to be treated as color
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PIL.Image: Reconstructed image
|
||||||
|
"""
|
||||||
|
# Perform inverse Fourier transform with original shape if provided
|
||||||
|
if original_shape is not None:
|
||||||
|
img_reconstructed = np.fft.irfft2(fft_spectrum, s=original_shape)
|
||||||
|
else:
|
||||||
|
img_reconstructed = np.fft.irfft2(fft_spectrum)
|
||||||
|
|
||||||
|
# Normalize values between 0 and 255
|
||||||
|
img_reconstructed = np.clip(img_reconstructed, 0, 255)
|
||||||
|
img_reconstructed = img_reconstructed.astype(np.uint8)
|
||||||
|
|
||||||
|
# Convert to PIL image
|
||||||
|
if is_color and img_reconstructed.ndim == 3:
|
||||||
|
pil_image = Image.fromarray(img_reconstructed, mode='RGB')
|
||||||
|
else:
|
||||||
|
pil_image = Image.fromarray(img_reconstructed, mode='L')
|
||||||
|
|
||||||
|
return pil_image
|
||||||
|
|
||||||
|
def rgb_to_yuv(rgb_array):
|
||||||
|
"""
|
||||||
|
Convert RGB to YUV color space.
|
||||||
|
Y = luminance, U and V = chrominance
|
||||||
|
"""
|
||||||
|
# Coefficients for RGB to YUV conversion
|
||||||
|
rgb_to_yuv_matrix = np.array([
|
||||||
|
[0.299, 0.587, 0.114], # Y
|
||||||
|
[-0.14713, -0.28886, 0.436], # U
|
||||||
|
[0.615, -0.51499, -0.10001] # V
|
||||||
|
])
|
||||||
|
|
||||||
|
# Reshape for matrix multiplication
|
||||||
|
original_shape = rgb_array.shape
|
||||||
|
rgb_flat = rgb_array.reshape(-1, 3)
|
||||||
|
|
||||||
|
# Apply transformation
|
||||||
|
yuv_flat = rgb_flat @ rgb_to_yuv_matrix.T
|
||||||
|
|
||||||
|
# Reshape back
|
||||||
|
yuv_array = yuv_flat.reshape(original_shape)
|
||||||
|
|
||||||
|
return yuv_array
|
||||||
|
|
||||||
|
def yuv_to_rgb(yuv_array):
|
||||||
|
"""
|
||||||
|
Convert YUV to RGB color space.
|
||||||
|
"""
|
||||||
|
# Coefficients for YUV to RGB conversion
|
||||||
|
yuv_to_rgb_matrix = np.array([
|
||||||
|
[1.0, 0.0, 1.13983], # R
|
||||||
|
[1.0, -0.39465, -0.58060], # G
|
||||||
|
[1.0, 2.03211, 0.0] # B
|
||||||
|
])
|
||||||
|
|
||||||
|
# Reshape for matrix multiplication
|
||||||
|
original_shape = yuv_array.shape
|
||||||
|
yuv_flat = yuv_array.reshape(-1, 3)
|
||||||
|
|
||||||
|
# Apply transformation
|
||||||
|
rgb_flat = yuv_flat @ yuv_to_rgb_matrix.T
|
||||||
|
|
||||||
|
# Reshape back
|
||||||
|
rgb_array = rgb_flat.reshape(original_shape)
|
||||||
|
|
||||||
|
return rgb_array
|
||||||
|
|
||||||
|
def erase_rainbow_artifacts(img, is_color):
|
||||||
|
"""
|
||||||
|
Remove rainbow artifacts from grayscale or color images.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
img: PIL Image (grayscale or RGB)
|
||||||
|
is_color: Boolean indicating if the image is to be treated as color
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PIL.Image: Cleaned image
|
||||||
|
"""
|
||||||
|
# Auto-detect color mode if not specified
|
||||||
|
if is_color is None:
|
||||||
|
color = img.mode in ('RGB', 'RGBA', 'L') and len(np.array(img).shape) == 3
|
||||||
|
|
||||||
|
if is_color and img.mode in ('RGB', 'RGBA'):
|
||||||
|
# Convert to RGB if needed
|
||||||
|
if img.mode == 'RGBA':
|
||||||
|
img = img.convert('RGB')
|
||||||
|
|
||||||
|
# Convert to numpy array
|
||||||
|
img_array = np.array(img, dtype=np.float32)
|
||||||
|
|
||||||
|
# Convert to YUV color space
|
||||||
|
yuv_array = rgb_to_yuv(img_array)
|
||||||
|
|
||||||
|
# Extract luminance channel (Y)
|
||||||
|
luminance = yuv_array[:, :, 0]
|
||||||
|
|
||||||
|
# Process only the luminance channel
|
||||||
|
fft_spectrum = fourier_transform_image(luminance)
|
||||||
|
clean_spectrum = attenuate_diagonal_frequencies(fft_spectrum)
|
||||||
|
clean_luminance = np.fft.irfft2(clean_spectrum, s=luminance.shape)
|
||||||
|
|
||||||
|
# Normalize and clip luminance
|
||||||
|
clean_luminance = np.clip(clean_luminance, 0, 255)
|
||||||
|
|
||||||
|
# Replace luminance in YUV array
|
||||||
|
yuv_array[:, :, 0] = clean_luminance
|
||||||
|
|
||||||
|
# Convert back to RGB
|
||||||
|
rgb_array = yuv_to_rgb(yuv_array)
|
||||||
|
rgb_array = np.clip(rgb_array, 0, 255).astype(np.uint8)
|
||||||
|
|
||||||
|
# Convert back to PIL image
|
||||||
|
clean_image = Image.fromarray(rgb_array, mode='RGB')
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Grayscale processing (original behavior)
|
||||||
|
if img.mode != 'L':
|
||||||
|
img = img.convert('L')
|
||||||
|
|
||||||
|
# Get original image dimensions
|
||||||
|
original_shape = (img.height, img.width)
|
||||||
|
|
||||||
|
fft_spectrum = fourier_transform_image(img)
|
||||||
|
clean_spectrum = attenuate_diagonal_frequencies(fft_spectrum)
|
||||||
|
clean_image = inverse_fourier_transform_image(clean_spectrum, is_color, original_shape)
|
||||||
|
|
||||||
|
return clean_image
|
||||||
@@ -120,6 +120,12 @@ def dependencyCheck(level):
|
|||||||
missing.append('Pillow 11.3.0+')
|
missing.append('Pillow 11.3.0+')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
missing.append('Pillow 11.3.0+')
|
missing.append('Pillow 11.3.0+')
|
||||||
|
try:
|
||||||
|
from pymupdf import __version__ as pymupdfVersion
|
||||||
|
if Version('1.26.1') > Version(pymupdfVersion):
|
||||||
|
missing.append('PyMuPDF 1.26.1+')
|
||||||
|
except ImportError:
|
||||||
|
missing.append('PyMuPDF 1.26.1+')
|
||||||
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)
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ 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
|
||||||
|
PyMuPDF>=1.26.1
|
||||||
|
|||||||
3
setup.py
3
setup.py
@@ -83,7 +83,8 @@ 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'
|
'numpy>=1.22.4',
|
||||||
|
'PyMuPDF>=1.26.1',
|
||||||
],
|
],
|
||||||
classifiers=[],
|
classifiers=[],
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
|||||||
Reference in New Issue
Block a user