mirror of
https://github.com/ciromattia/kcc
synced 2026-04-18 06:58:58 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8861299d24 | ||
|
|
636447bb62 | ||
|
|
b23c7744cb | ||
|
|
2398a5b1ac | ||
|
|
2b2ac8ff55 | ||
|
|
5209d9a7b8 | ||
|
|
5336870097 | ||
|
|
4371d14391 | ||
|
|
f96b7cb22b | ||
|
|
4dfd2ea942 | ||
|
|
ba7f4336a5 | ||
|
|
9561b04bec | ||
|
|
2a8f8e9ab4 | ||
|
|
b9cef59912 | ||
|
|
f2ab730691 | ||
|
|
44401583e4 | ||
|
|
28faf524c4 | ||
|
|
2d288f72ea | ||
|
|
fb9b3c676b | ||
|
|
cff1de4fa5 |
1
.github/workflows/package-macos.yml
vendored
1
.github/workflows/package-macos.yml
vendored
@@ -89,7 +89,6 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
LICENSE.txt
|
|
||||||
dist/*.dmg
|
dist/*.dmg
|
||||||
- name: Clean up keychain and provisioning profile
|
- name: Clean up keychain and provisioning profile
|
||||||
# TODO signing
|
# TODO signing
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
|
||||||
|
|
||||||
name: build KCC for windows with docker
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*.*.*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
entry: [ kcc-c2e, kcc-c2p ]
|
|
||||||
include:
|
|
||||||
- entry: kcc-c2e
|
|
||||||
capital: KCC_c2e
|
|
||||||
- entry: kcc-c2p
|
|
||||||
capital: KCC_c2p
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Package Application
|
|
||||||
uses: JackMcKew/pyinstaller-action-windows@main
|
|
||||||
with:
|
|
||||||
path: .
|
|
||||||
spec: ./${{ matrix.entry }}.spec
|
|
||||||
- name: rename binaries
|
|
||||||
run: |
|
|
||||||
version_built=$(cat kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/[^.0-9b]//g")
|
|
||||||
mv dist/windows/${{ matrix.entry }}.exe dist/windows/${{ matrix.capital }}_${version_built}.exe
|
|
||||||
|
|
||||||
- name: upload-unsigned-artifact
|
|
||||||
id: upload-unsigned-artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows-build-${{ matrix.entry }}
|
|
||||||
path: dist/windows/*.exe
|
|
||||||
|
|
||||||
- id: optional_step_id
|
|
||||||
uses: signpath/github-action-submit-signing-request@v1.3
|
|
||||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
|
||||||
with:
|
|
||||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
|
||||||
organization-id: '1dc1bad6-4a8c-4f85-af30-5c5d3d392ea6'
|
|
||||||
project-slug: 'kcc'
|
|
||||||
signing-policy-slug: 'release-signing'
|
|
||||||
github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}'
|
|
||||||
wait-for-completion: true
|
|
||||||
output-artifact-directory: 'dist/windows/'
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
with:
|
|
||||||
prerelease: true
|
|
||||||
generate_release_notes: true
|
|
||||||
files: |
|
|
||||||
LICENSE.txt
|
|
||||||
dist/windows/*.exe
|
|
||||||
15
.github/workflows/package-windows.yml
vendored
15
.github/workflows/package-windows.yml
vendored
@@ -23,6 +23,16 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
entry: [ kcc, kcc-c2e, kcc-c2p ]
|
||||||
|
include:
|
||||||
|
- entry: kcc
|
||||||
|
command: build_binary
|
||||||
|
- entry: kcc-c2e
|
||||||
|
command: build_c2e
|
||||||
|
- entry: kcc-c2p
|
||||||
|
command: build_c2p
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
@@ -40,12 +50,12 @@ jobs:
|
|||||||
pip install certifi pyinstaller --no-binary pyinstaller
|
pip install certifi pyinstaller --no-binary pyinstaller
|
||||||
- name: build binary
|
- name: build binary
|
||||||
run: |
|
run: |
|
||||||
python setup.py build_binary
|
python setup.py ${{ matrix.command }}
|
||||||
- name: upload-unsigned-artifact
|
- name: upload-unsigned-artifact
|
||||||
id: upload-unsigned-artifact
|
id: upload-unsigned-artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-build
|
name: windows-build-${{ matrix.entry }}
|
||||||
path: dist/*.exe
|
path: dist/*.exe
|
||||||
- id: optional_step_id
|
- id: optional_step_id
|
||||||
uses: signpath/github-action-submit-signing-request@v1.3
|
uses: signpath/github-action-submit-signing-request@v1.3
|
||||||
@@ -65,5 +75,4 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
LICENSE.txt
|
|
||||||
dist/*.exe
|
dist/*.exe
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ dist/
|
|||||||
build/
|
build/
|
||||||
KindleComicConverter*.egg-info/
|
KindleComicConverter*.egg-info/
|
||||||
.idea/
|
.idea/
|
||||||
|
.vscode/
|
||||||
win7
|
win7
|
||||||
osx10.11
|
osx10.11
|
||||||
/venv/
|
/venv/
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
|
|||||||
- MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable.
|
- MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable.
|
||||||
- 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
|
||||||
|
- Kindle panel view not working?
|
||||||
|
- Virtual panel view is enabled in Aa menu on your Kindle, not in KCC as of 7.4
|
||||||
- Right to left mode not working?
|
- Right to left mode not working?
|
||||||
- RTL mode only affects splitting order for CBZ output. Your cbz reader itself sets the page turn direction.
|
- RTL mode only affects splitting order for CBZ output. Your cbz reader itself sets the page turn direction.
|
||||||
- Colors inverted?
|
- Colors inverted?
|
||||||
@@ -272,6 +274,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.
|
||||||
|
--filefusion Combines all input files into a single file.
|
||||||
--eraserainbow Erase rainbow effect on color eink screen by attenuating interfering frequencies
|
--eraserainbow Erase rainbow effect on color eink screen by attenuating interfering frequencies
|
||||||
|
|
||||||
CUSTOM PROFILE:
|
CUSTOM PROFILE:
|
||||||
|
|||||||
@@ -416,7 +416,7 @@
|
|||||||
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>3200</number>
|
<number>6000</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -442,7 +442,7 @@
|
|||||||
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>5120</number>
|
<number>8000</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ class WorkerThread(QThread):
|
|||||||
if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '':
|
if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '':
|
||||||
MW.showDialog.emit("KindleGen error:\n\n" + self.kindlegenErrorCode[1], 'error')
|
MW.showDialog.emit("KindleGen error:\n\n" + self.kindlegenErrorCode[1], 'error')
|
||||||
if self.kindlegenErrorCode[0] == 23026:
|
if self.kindlegenErrorCode[0] == 23026:
|
||||||
MW.addMessage.emit('Created EPUB file was too big.', 'error', False)
|
MW.addMessage.emit('Created EPUB file was too big. Weird file structure?', 'error', False)
|
||||||
MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error',
|
MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error',
|
||||||
False)
|
False)
|
||||||
if self.kindlegenErrorCode[0] == 3221226505:
|
if self.kindlegenErrorCode[0] == 3221226505:
|
||||||
@@ -876,6 +876,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
GUI.chunkSizeCheckBox.setChecked(False)
|
GUI.chunkSizeCheckBox.setChecked(False)
|
||||||
elif not GUI.webtoonBox.isChecked():
|
elif not GUI.webtoonBox.isChecked():
|
||||||
GUI.chunkSizeCheckBox.setEnabled(True)
|
GUI.chunkSizeCheckBox.setEnabled(True)
|
||||||
|
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'PDF') and not GUI.webtoonBox.isChecked():
|
||||||
|
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
|
||||||
|
|
||||||
def stripTags(self, html):
|
def stripTags(self, html):
|
||||||
s = HTMLStripper()
|
s = HTMLStripper()
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.widthBox = QSpinBox(self.customWidget)
|
self.widthBox = QSpinBox(self.customWidget)
|
||||||
self.widthBox.setObjectName(u"widthBox")
|
self.widthBox.setObjectName(u"widthBox")
|
||||||
self.widthBox.setMaximum(3200)
|
self.widthBox.setMaximum(6000)
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
|
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.heightBox = QSpinBox(self.customWidget)
|
self.heightBox = QSpinBox(self.customWidget)
|
||||||
self.heightBox.setObjectName(u"heightBox")
|
self.heightBox.setObjectName(u"heightBox")
|
||||||
self.heightBox.setMaximum(5120)
|
self.heightBox.setMaximum(8000)
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
|
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '9.2.0'
|
__version__ = '9.2.2'
|
||||||
__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'
|
||||||
|
|||||||
@@ -71,12 +71,23 @@ def main(argv=None):
|
|||||||
if len(sources) == 0:
|
if len(sources) == 0:
|
||||||
print('No matching files found.')
|
print('No matching files found.')
|
||||||
return 1
|
return 1
|
||||||
|
if options.filefusion:
|
||||||
|
fusion_path = makeFusion(list(sources))
|
||||||
|
sources.clear()
|
||||||
|
sources.add(fusion_path)
|
||||||
for source in sources:
|
for source in sources:
|
||||||
source = source.rstrip('\\').rstrip('/')
|
source = source.rstrip('\\').rstrip('/')
|
||||||
options = copy(args)
|
options = copy(args)
|
||||||
options = checkOptions(options)
|
options = checkOptions(options)
|
||||||
print('Working on ' + source + '...')
|
print('Working on ' + source + '...')
|
||||||
makeBook(source)
|
makeBook(source)
|
||||||
|
|
||||||
|
if options.filefusion:
|
||||||
|
for path in sources:
|
||||||
|
if os.path.isfile(path):
|
||||||
|
os.remove(path)
|
||||||
|
elif os.path.isdir(path):
|
||||||
|
rmtree(path, True)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -1330,6 +1341,8 @@ def makeParser():
|
|||||||
help="Disable autocontrast.")
|
help="Disable autocontrast.")
|
||||||
output_options.add_argument("--colorautocontrast", action="store_true", dest="colorautocontrast", default=False,
|
output_options.add_argument("--colorautocontrast", action="store_true", dest="colorautocontrast", default=False,
|
||||||
help="Autocontrast color pages too. Skipped for pages without near blacks or whites.")
|
help="Autocontrast color pages too. Skipped for pages without near blacks or whites.")
|
||||||
|
output_options.add_argument("--filefusion", action="store_true", dest="filefusion", default=False,
|
||||||
|
help="Combines all input files into a single file.")
|
||||||
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",
|
||||||
|
|||||||
@@ -24,10 +24,12 @@ import sys
|
|||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
from PIL import Image, ImageChops, ImageOps, ImageDraw, ImageFilter
|
from PIL import Image, ImageChops, ImageOps, ImageDraw, ImageFilter, ImageFile
|
||||||
from PIL.Image import Dither
|
from PIL.Image import Dither
|
||||||
from .shared import dot_clean, getImageFileName, walkLevel, walkSort, sanitizeTrace
|
from .shared import dot_clean, getImageFileName, walkLevel, walkSort, sanitizeTrace
|
||||||
|
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
||||||
def mergeDirectoryTick(output):
|
def mergeDirectoryTick(output):
|
||||||
if output:
|
if output:
|
||||||
@@ -60,8 +62,8 @@ def mergeDirectory(work):
|
|||||||
imagesValid.append(i[0])
|
imagesValid.append(i[0])
|
||||||
# Silently drop directories that contain too many images
|
# Silently drop directories that contain too many images
|
||||||
# 131072 = GIMP_MAX_IMAGE_SIZE / 4
|
# 131072 = GIMP_MAX_IMAGE_SIZE / 4
|
||||||
if targetHeight > 131072 * 2:
|
if targetHeight > 131072 * 3:
|
||||||
raise RuntimeError(f'Image too tall at {targetHeight} pixels.')
|
raise RuntimeError(f'Image too tall at {targetHeight} pixels. {targetWidth} pixels wide. Try using separate chapter folders or file fusion.')
|
||||||
result = Image.new('RGB', (targetWidth, targetHeight))
|
result = Image.new('RGB', (targetWidth, targetHeight))
|
||||||
y = 0
|
y = 0
|
||||||
for i in imagesValid:
|
for i in imagesValid:
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ import numpy as np
|
|||||||
from pathlib import Path
|
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, ImageFile, ImageChops, ImageDraw
|
||||||
|
|
||||||
from .rainbow_artifacts_eraser import erase_rainbow_artifacts
|
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
|
||||||
|
|
||||||
AUTO_CROP_THRESHOLD = 0.015
|
AUTO_CROP_THRESHOLD = 0.015
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
||||||
class ProfileData:
|
class ProfileData:
|
||||||
@@ -296,9 +297,9 @@ class ComicPage:
|
|||||||
|
|
||||||
# cut off pixels from both ends of the histogram to remove jpg compression artifacts
|
# cut off pixels from both ends of the histogram to remove jpg compression artifacts
|
||||||
# for better accuracy, you could split the image in half and analyze each half separately
|
# for better accuracy, you could split the image in half and analyze each half separately
|
||||||
def histograms_cutoff(self, cb, cr, cutoff=(2, 2)):
|
def histograms_cutoff(self, cb_hist, cr_hist, cutoff=(2, 2)):
|
||||||
cb_hist = cb.histogram()
|
if cutoff == (0, 0):
|
||||||
cr_hist = cr.histogram()
|
return cb_hist, cr_hist
|
||||||
|
|
||||||
for h in cb_hist, cr_hist:
|
for h in cb_hist, cr_hist:
|
||||||
# get number of pixels
|
# get number of pixels
|
||||||
@@ -327,60 +328,50 @@ class ComicPage:
|
|||||||
break
|
break
|
||||||
return cb_hist, cr_hist
|
return cb_hist, cr_hist
|
||||||
|
|
||||||
|
def color_precision(self, cb_hist_original, cr_hist_original, cutoff, diff_threshold):
|
||||||
|
cb_hist, cr_hist = self.histograms_cutoff(cb_hist_original.copy(), cr_hist_original.copy(), cutoff)
|
||||||
|
|
||||||
|
cb_nonzero = [i for i, e in enumerate(cb_hist) if e]
|
||||||
|
cr_nonzero = [i for i, e in enumerate(cr_hist) if e]
|
||||||
|
cb_spread = cb_nonzero[-1] - cb_nonzero[0]
|
||||||
|
cr_spread = cr_nonzero[-1] - cr_nonzero[0]
|
||||||
|
|
||||||
|
# bias adjustment, don't go lower than 7
|
||||||
|
SPREAD_THRESHOLD = 7
|
||||||
|
if self.opt.forcecolor:
|
||||||
|
if any([
|
||||||
|
cb_nonzero[0] > 128,
|
||||||
|
cr_nonzero[0] > 128,
|
||||||
|
cb_nonzero[-1] < 128,
|
||||||
|
cr_nonzero[-1] < 128,
|
||||||
|
]):
|
||||||
|
return True, True
|
||||||
|
elif cb_spread < SPREAD_THRESHOLD and cr_spread < SPREAD_THRESHOLD:
|
||||||
|
return True, False
|
||||||
|
|
||||||
|
DIFF_THRESHOLD = diff_threshold
|
||||||
|
if any([
|
||||||
|
cb_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
||||||
|
cr_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
||||||
|
cb_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
||||||
|
cr_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
||||||
|
]):
|
||||||
|
return True, True
|
||||||
|
|
||||||
|
return False, None
|
||||||
|
|
||||||
def calculate_color(self):
|
def calculate_color(self):
|
||||||
img = self.image.convert("YCbCr")
|
img = self.image.convert("YCbCr")
|
||||||
_, cb, cr = img.split()
|
_, cb, cr = img.split()
|
||||||
|
cb_hist_original = cb.histogram()
|
||||||
|
cr_hist_original = cr.histogram()
|
||||||
|
|
||||||
# get rid of some jpg compression
|
# you can increase 22 but don't increase 10. 4 maybe can go higher
|
||||||
cutoff = (.2, .2)
|
for cutoff, diff_threshold in [((0, 0), 22), ((.2, .2), 10), ((3, 3), 4)]:
|
||||||
cb_hist, cr_hist = self.histograms_cutoff(cb, cr, cutoff)
|
done, decision = self.color_precision(cb_hist_original, cr_hist_original, cutoff, diff_threshold)
|
||||||
|
if done:
|
||||||
cb_nonzero = [i for i, e in enumerate(cb_hist) if e]
|
return decision
|
||||||
cr_nonzero = [i for i, e in enumerate(cr_hist) if e]
|
return False
|
||||||
cb_spread = cb_nonzero[-1] - cb_nonzero[0]
|
|
||||||
cr_spread = cr_nonzero[-1] - cr_nonzero[0]
|
|
||||||
|
|
||||||
# bias adjustment
|
|
||||||
SPREAD_THRESHOLD = 5
|
|
||||||
if not self.opt.forcecolor and cb_spread < SPREAD_THRESHOLD and cr_spread < SPREAD_THRESHOLD:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# check for large amount of extreme colors
|
|
||||||
# 11 if too high. 10 is barely enough. If needed make it magnitude of both
|
|
||||||
DIFF_THRESHOLD = 10
|
|
||||||
if any([
|
|
||||||
cb_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
|
||||||
cr_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
|
||||||
cb_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
|
||||||
cr_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
|
||||||
]):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# get ride of most jpg compression
|
|
||||||
cutoff = (2, 2)
|
|
||||||
cb_hist, cr_hist = self.histograms_cutoff(cb, cr, cutoff)
|
|
||||||
|
|
||||||
cb_nonzero = [i for i, e in enumerate(cb_hist) if e]
|
|
||||||
cr_nonzero = [i for i, e in enumerate(cr_hist) if e]
|
|
||||||
cb_spread = cb_nonzero[-1] - cb_nonzero[0]
|
|
||||||
cr_spread = cr_nonzero[-1] - cr_nonzero[0]
|
|
||||||
|
|
||||||
# bias adjustment
|
|
||||||
SPREAD_THRESHOLD = 5
|
|
||||||
if not self.opt.forcecolor and cb_spread < SPREAD_THRESHOLD and cr_spread < SPREAD_THRESHOLD:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# check for any amount of mild colors still remaining
|
|
||||||
DIFF_THRESHOLD = 6
|
|
||||||
if any([
|
|
||||||
cb_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
|
||||||
cr_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
|
||||||
cb_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
|
||||||
cr_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
|
||||||
]):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def saveToDir(self):
|
def saveToDir(self):
|
||||||
try:
|
try:
|
||||||
@@ -405,7 +396,7 @@ class ComicPage:
|
|||||||
|
|
||||||
def save_with_codec(self, image, targetPath):
|
def save_with_codec(self, image, targetPath):
|
||||||
if self.opt.forcepng:
|
if self.opt.forcepng:
|
||||||
image.info["transparency"] = None
|
image.info.pop('transparency', None)
|
||||||
if self.opt.iskindle and ('MOBI' in self.opt.format or 'EPUB' in self.opt.format):
|
if self.opt.iskindle and ('MOBI' in self.opt.format or 'EPUB' in self.opt.format):
|
||||||
targetPath += '.gif'
|
targetPath += '.gif'
|
||||||
image.save(targetPath, 'GIF', optimize=1, interlace=False)
|
image.save(targetPath, 'GIF', optimize=1, interlace=False)
|
||||||
@@ -557,7 +548,7 @@ class Cover:
|
|||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
self.image = self.image.convert('RGB')
|
self.image = self.image.convert('RGB')
|
||||||
self.image = ImageOps.autocontrast(self.image)
|
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
|
||||||
if not self.options.forcecolor:
|
if not self.options.forcecolor:
|
||||||
self.image = self.image.convert('L')
|
self.image = self.image.convert('L')
|
||||||
self.crop_main_cover()
|
self.crop_main_cover()
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from PIL import Image, ImageFilter, ImageOps
|
from PIL import Image, ImageFilter, ImageOps, ImageFile
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
from .common_crop import threshold_from_power, group_close_values
|
from .common_crop import threshold_from_power, group_close_values
|
||||||
|
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Crops inter-panel empty spaces (ignores empty spaces near borders - for that use crop margins).
|
Crops inter-panel empty spaces (ignores empty spaces near borders - for that use crop margins).
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from PIL import ImageOps, ImageFilter
|
from PIL import ImageOps, ImageFilter, ImageFile
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from .common_crop import threshold_from_power, group_close_values
|
from .common_crop import threshold_from_power, group_close_values
|
||||||
|
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Some assupmptions on the page number sizes
|
Some assupmptions on the page number sizes
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image, ImageFile
|
||||||
|
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
||||||
def fourier_transform_image(img):
|
def fourier_transform_image(img):
|
||||||
"""
|
"""
|
||||||
|
|||||||
67
setup.py
67
setup.py
@@ -8,6 +8,8 @@ Install as Python package:
|
|||||||
|
|
||||||
Create EXE/APP:
|
Create EXE/APP:
|
||||||
python3 setup.py build_binary
|
python3 setup.py build_binary
|
||||||
|
python3 setup.py build_c2e
|
||||||
|
python3 setup.py build_c2p
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -57,10 +59,75 @@ class BuildBinaryCommand(setuptools.Command):
|
|||||||
else:
|
else:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
class BuildC2ECommand(setuptools.Command):
|
||||||
|
description = 'build binary c2e release'
|
||||||
|
user_options = []
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
def run(self):
|
||||||
|
VERSION = __version__
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "KCC C2E" -c -s kcc-c2e.py')
|
||||||
|
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
if os.getenv('WINDOWS_7'):
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n kcc_c2e_win7_legacy_' + VERSION + ' -c --noupx kcc-c2e.py')
|
||||||
|
else:
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n kcc_c2e_' + VERSION + ' -c --noupx kcc-c2e.py')
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.platform == 'linux':
|
||||||
|
os.system(
|
||||||
|
'pyinstaller --hidden-import=_cffi_backend --hidden-import=queue -y -F -i icons/comic2ebook.ico -n kcc_c2e_linux_' + VERSION + ' kcc-c2e.py')
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
class BuildC2PCommand(setuptools.Command):
|
||||||
|
description = 'build binary c2p release'
|
||||||
|
user_options = []
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
def run(self):
|
||||||
|
VERSION = __version__
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -n "KCC C2P" -c -s kcc-c2p.py')
|
||||||
|
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
if os.getenv('WINDOWS_7'):
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n kcc_c2p_win7_legacy_' + VERSION + ' -c --noupx kcc-c2p.py')
|
||||||
|
else:
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n kcc_c2p_' + VERSION + ' -c --noupx kcc-c2p.py')
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.platform == 'linux':
|
||||||
|
os.system(
|
||||||
|
'pyinstaller --hidden-import=_cffi_backend --hidden-import=queue -y -F -i icons/comic2ebook.ico -n kcc_c2p_linux_' + VERSION + ' kcc-c2p.py')
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
cmdclass={
|
cmdclass={
|
||||||
'build_binary': BuildBinaryCommand,
|
'build_binary': BuildBinaryCommand,
|
||||||
|
'build_c2e': BuildC2ECommand,
|
||||||
|
'build_c2p': BuildC2PCommand,
|
||||||
},
|
},
|
||||||
name=NAME,
|
name=NAME,
|
||||||
version=VERSION,
|
version=VERSION,
|
||||||
|
|||||||
Reference in New Issue
Block a user