Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b67d4019e | ||
|
|
2423e2e44a | ||
|
|
eb694b2dc3 | ||
|
|
04aac3ffc0 | ||
|
|
0f630c5804 | ||
|
|
d272073748 | ||
|
|
66c6b8611a | ||
|
|
1b3b619491 | ||
|
|
fb784a2eb4 | ||
|
|
f2deb1061f | ||
|
|
3be76ec89f | ||
|
|
38fa13610f | ||
|
|
6d3bd0296c | ||
|
|
9decf0e68e | ||
|
|
dc2cc8e7b8 | ||
|
|
55468019dd | ||
|
|
8f191e604b | ||
|
|
924f83d976 | ||
|
|
52d52ff323 | ||
|
|
e93be5cf68 | ||
|
|
4cdbb4beef | ||
|
|
2c9056c524 | ||
|
|
a503f54f9f |
@@ -27,7 +27,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.11
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.11
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
# apply provisioning profile
|
# apply provisioning profile
|
||||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||||
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
|
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
- run: npm install -g appdmg
|
- run: npm install -g appdmg
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
pip3 install --upgrade pip pyinstaller certifi
|
pip3 install --upgrade pip pyinstaller certifi
|
||||||
pip3 install --upgrade -r requirements-osx-legacy.txt
|
pip3 install --upgrade -r requirements-osx-legacy.txt
|
||||||
./gen_ui_files.sh
|
./gen_ui_files.sh
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 16
|
||||||
- run: npm install -g appdmg
|
- run: npm install -g appdmg
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: 3.11
|
python-version: 3.11
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
name: windows-build-${{ matrix.entry }}
|
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@v2.2
|
uses: signpath/github-action-submit-signing-request@v2.3
|
||||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||||
with:
|
with:
|
||||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v7
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
name: windows7-build
|
name: windows7-build
|
||||||
path: dist/*.exe
|
path: dist/*.exe
|
||||||
- id: optional_step_id
|
- id: optional_step_id
|
||||||
uses: signpath/github-action-submit-signing-request@v2.2
|
uses: signpath/github-action-submit-signing-request@v2.3
|
||||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||||
with:
|
with:
|
||||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Most manga conversion methods for ereaders add margins. But KCC enables true fullscreen from top to bottom.
|
Most manga conversion methods for ereaders add margins. But KCC enables true fullscreen from top to bottom.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
(feat. Ya Boy Kongming! and Fire Force from a [Humble Bundle](https://humblebundleinc.sjv.io/xL6Zv1) PDF source.)
|
(feat. Ya Boy Kongming! and Fire Force from a [Humble Bundle](https://humblebundleinc.sjv.io/xL6Zv1) PDF source.)
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ KCC runs on Windows, macOS, and Linux.
|
|||||||
|
|
||||||
Supported input formats include JPG/PNG image files in folders, archives like CBZ/EPUB, or PDF.
|
Supported input formats include JPG/PNG image files in folders, archives like CBZ/EPUB, or PDF.
|
||||||
|
|
||||||
Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, and PDF.
|
Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, PDF, and folders of images.
|
||||||
|
|
||||||
The absolute highest quality source files are DRM-free PDFs from [Humble Bundle](https://humblebundleinc.sjv.io/xL6Zv1)/Fanatical.
|
The absolute highest quality source files are DRM-free PDFs from [Humble Bundle](https://humblebundleinc.sjv.io/xL6Zv1)/Fanatical.
|
||||||
The PDFs are often x12000 resolution or better, while even the 10" Kindle Scribe is only x2480.
|
The PDFs are often x12000 resolution or better, while even the 10" Kindle Scribe is only x2480.
|
||||||
@@ -33,42 +33,42 @@ KCC's main goal is maximum image quality at significantly smaller file size. For
|
|||||||
|
|
||||||
2) fixes black levels to avoid gray/faded blacks found in many Kindle Store manga
|
2) fixes black levels to avoid gray/faded blacks found in many Kindle Store manga
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
3) fixes [the rainbow effect on Kaleido 3 color eink](https://www.youtube.com/watch?v=Dw2HTJCGMhw) without blur:
|
3) fixes [the rainbow effect on Kaleido 3 color eink](https://www.youtube.com/watch?v=Dw2HTJCGMhw) without blur:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
4) can [semi-automatically detect 2-page spreads](https://www.youtube.com/watch?v=kfIX67f7Aqk) from pre-split single pages:
|
4) can [semi-automatically detect 2-page spreads](https://www.youtube.com/watch?v=kfIX67f7Aqk) from pre-split single pages:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
You can view combined spreads either rotated or not:
|
You can view combined spreads either rotated or not:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
You can view the split halves either before or after combined spreads:
|
You can view the split halves either before or after combined spreads:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
Just drop your input files into the KCC window, hit convert, and USB drop the output files onto your device's `documents` folder!
|
Just drop your input files into the KCC window, hit convert, and USB drop the output files onto your device's `documents` folder!
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/da73d625-e082-482d-91a4-ae4765e96fd7
|

|
||||||
|
|
||||||
|
There are a lot of options, but most people only care about the ones checked above.
|
||||||
|
|
||||||
Then all your usb loaded comic files will show up next to all your normal books in your ereader library!
|
Then all your usb loaded comic files will show up next to all your normal books in your ereader library!
|
||||||
|
|
||||||
|
Here's a quick video demo of how the process works (using expert mode):
|
||||||
|
|
||||||
|
https://github.com/user-attachments/assets/da73d625-e082-482d-91a4-ae4765e96fd7
|
||||||
|
|
||||||
The final result looks incredible on my 10" Kindle Scribe: https://www.youtube.com/watch?v=2CIHW2N9Enc
|
The final result looks incredible on my 10" Kindle Scribe: https://www.youtube.com/watch?v=2CIHW2N9Enc
|
||||||
|
|
||||||
**WARNING**: Kindle Scribe 2025 MOBI support may have blank pages. Use PDF instead.
|
**WARNING**: Kindle Scribe 2025 MOBI support may have blank pages. Use PDF instead.
|
||||||
|
|
||||||
The GUI looks like this, built in Qt6.
|
|
||||||
There are a lot of options, but most people only care about the ones checked below.
|
|
||||||
Hover over each option to see details in a tooltip.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac or OpenMTP.
|
If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac or OpenMTP.
|
||||||
|
|
||||||
YouTube tutorial (please subscribe): https://www.youtube.com/watch?v=QQ6zJcMF2Iw
|
YouTube tutorial (please subscribe): https://www.youtube.com/watch?v=QQ6zJcMF2Iw
|
||||||
@@ -175,8 +175,6 @@ You'll need to install various tools to access important but optional features.
|
|||||||
|
|
||||||
### KindleGen
|
### KindleGen
|
||||||
|
|
||||||
(This is not needed for Koreader users on jailbroken Kindle. Just set output to CBZ instead.)
|
|
||||||
|
|
||||||
On Windows and macOS, install [Kindle Previewer](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011) and `kindlegen` will be autodetected from it.
|
On Windows and macOS, install [Kindle Previewer](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011) and `kindlegen` will be autodetected from it.
|
||||||
|
|
||||||
If you have issues detecting it, get stuck on the MOBI conversion step, or use Linux AppImage or Flatpak, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation#kindlegen
|
If you have issues detecting it, get stuck on the MOBI conversion step, or use Linux AppImage or Flatpak, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation#kindlegen
|
||||||
@@ -287,6 +285,7 @@ PROCESSING:
|
|||||||
--pdfwidth Render vector PDFs based on device width instead of height.
|
--pdfwidth Render vector PDFs based on device width instead of height.
|
||||||
-u, --upscale Resize images smaller than device's resolution
|
-u, --upscale Resize images smaller than device's resolution
|
||||||
-s, --stretch Stretch images to device's resolution
|
-s, --stretch Stretch images to device's resolution
|
||||||
|
--wallpaper Crop images to to fill screen
|
||||||
-r SPLITTER, --splitter SPLITTER
|
-r SPLITTER, --splitter SPLITTER
|
||||||
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
|
||||||
|
|||||||
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 536 KiB |
|
After Width: | Height: | Size: 619 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 513 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 181 KiB |
@@ -127,6 +127,8 @@ class Icons:
|
|||||||
self.MOBIFormat.addPixmap(QPixmap(":/Formats/icons/MOBI.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
self.MOBIFormat.addPixmap(QPixmap(":/Formats/icons/MOBI.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.CBZFormat = QIcon()
|
self.CBZFormat = QIcon()
|
||||||
self.CBZFormat.addPixmap(QPixmap(":/Formats/icons/CBZ.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
self.CBZFormat.addPixmap(QPixmap(":/Formats/icons/CBZ.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.FOLDERFormat = QIcon()
|
||||||
|
self.FOLDERFormat.addPixmap(QPixmap(":/Other/icons/folder_new.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.EPUBFormat = QIcon()
|
self.EPUBFormat = QIcon()
|
||||||
self.EPUBFormat.addPixmap(QPixmap(":/Formats/icons/EPUB.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
self.EPUBFormat.addPixmap(QPixmap(":/Formats/icons/EPUB.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.KFXFormat = QIcon()
|
self.KFXFormat = QIcon()
|
||||||
@@ -153,17 +155,21 @@ class Icons:
|
|||||||
|
|
||||||
|
|
||||||
class VersionThread(QThread):
|
class VersionThread(QThread):
|
||||||
def __init__(self):
|
def __init__(self, startNumber2):
|
||||||
QThread.__init__(self)
|
QThread.__init__(self)
|
||||||
self.newVersion = ''
|
self.newVersion = ''
|
||||||
self.md5 = ''
|
self.md5 = ''
|
||||||
self.barProgress = 0
|
self.barProgress = 0
|
||||||
self.answer = None
|
self.answer = None
|
||||||
|
self.startNumber2 = startNumber2
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
if self.startNumber2 < 5:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# unauthenticated API requests limit is 60 req/hour
|
# unauthenticated API requests limit is 60 req/hour
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
@@ -251,6 +257,8 @@ def get_options():
|
|||||||
options.righttoleft = True
|
options.righttoleft = True
|
||||||
if GUI.lightnovelBox.isChecked():
|
if GUI.lightnovelBox.isChecked():
|
||||||
options.lightnovel = True
|
options.lightnovel = True
|
||||||
|
if GUI.wallpaperBox.isChecked():
|
||||||
|
options.wallpaper = True
|
||||||
if GUI.ebokBox.isChecked():
|
if GUI.ebokBox.isChecked():
|
||||||
options.ebok = True
|
options.ebok = True
|
||||||
if GUI.invertDirectionBox.isChecked():
|
if GUI.invertDirectionBox.isChecked():
|
||||||
@@ -442,6 +450,9 @@ class WorkerThread(QThread):
|
|||||||
if gui_current_format == 'CBZ':
|
if gui_current_format == 'CBZ':
|
||||||
MW.addMessage.emit('Creating CBZ files', 'info', False)
|
MW.addMessage.emit('Creating CBZ files', 'info', False)
|
||||||
GUI.progress.content = 'Creating CBZ files'
|
GUI.progress.content = 'Creating CBZ files'
|
||||||
|
elif gui_current_format == 'FOLDER':
|
||||||
|
MW.addMessage.emit('Creating folders', 'info', False)
|
||||||
|
GUI.progress.content = 'Creating folders'
|
||||||
elif gui_current_format == 'PDF':
|
elif gui_current_format == 'PDF':
|
||||||
MW.addMessage.emit('Creating PDF files', 'info', False)
|
MW.addMessage.emit('Creating PDF files', 'info', False)
|
||||||
GUI.progress.content = 'Creating PDF files'
|
GUI.progress.content = 'Creating PDF files'
|
||||||
@@ -487,6 +498,8 @@ class WorkerThread(QThread):
|
|||||||
GUI.progress.content = ''
|
GUI.progress.content = ''
|
||||||
if gui_current_format == 'CBZ':
|
if gui_current_format == 'CBZ':
|
||||||
MW.addMessage.emit('Creating CBZ files... <b>Done!</b>', 'info', True)
|
MW.addMessage.emit('Creating CBZ files... <b>Done!</b>', 'info', True)
|
||||||
|
elif gui_current_format == 'FOLDER':
|
||||||
|
MW.addMessage.emit('Creating folders... <b>Done!</b>', 'info', True)
|
||||||
elif gui_current_format == 'PDF':
|
elif gui_current_format == 'PDF':
|
||||||
MW.addMessage.emit('Creating PDF files... <b>Done!</b>', 'info', True)
|
MW.addMessage.emit('Creating PDF files... <b>Done!</b>', 'info', True)
|
||||||
else:
|
else:
|
||||||
@@ -671,9 +684,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
GUI.jobList.scrollToBottom()
|
GUI.jobList.scrollToBottom()
|
||||||
|
|
||||||
def labelSpreadsStart(self):
|
def labelSpreadsStart(self):
|
||||||
low_quality_preview = True
|
low_quality_preview = False
|
||||||
if QApplication.keyboardModifiers() == Qt.ShiftModifier:
|
if QApplication.keyboardModifiers() == Qt.ShiftModifier:
|
||||||
low_quality_preview = False
|
low_quality_preview = True
|
||||||
currentJobs = []
|
currentJobs = []
|
||||||
# TODO: make this a function since it's copy pasted
|
# TODO: make this a function since it's copy pasted
|
||||||
for i in range(GUI.jobList.count()):
|
for i in range(GUI.jobList.count()):
|
||||||
@@ -708,19 +721,22 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
# TODO: with statements
|
# TODO: with statements
|
||||||
# TODO: ignore 1% of top and bottom too?
|
# TODO: ignore 1% of top and bottom too?
|
||||||
im1 = Image.open(os.path.join(root, files[i]))
|
im1 = Image.open(os.path.join(root, files[i]))
|
||||||
|
im2 = Image.open(os.path.join(root, files[i+1]))
|
||||||
|
if not options.righttoleft:
|
||||||
|
im1, im2 = im2, im1
|
||||||
size1 = im1.size
|
size1 = im1.size
|
||||||
|
size2 = im2.size
|
||||||
|
|
||||||
preview_crop1 = im1.crop((0, 0, 0.2*size1[0], size1[1]))
|
preview_crop1 = im1.crop((0, 0, 0.2*size1[0], size1[1]))
|
||||||
if low_quality_preview:
|
if low_quality_preview:
|
||||||
preview_crop1 = preview_crop1.convert('1', dither=Dither.NONE)
|
preview_crop1 = preview_crop1.convert('1', dither=Dither.NONE)
|
||||||
calculation_crop1 = im1.crop((0.01*size1[0], 0, 0.04*size1[0], size1[1])).convert('1', dither=Dither.NONE)
|
calculation_crop1 = im1.crop((0.01*size1[0], 0, 0.04*size1[0], size1[1])).convert('1', dither=Dither.NONE)
|
||||||
#crop1 = crop11
|
|
||||||
im2 = Image.open(os.path.join(root, files[i+1]))
|
|
||||||
size2 = im2.size
|
|
||||||
preview_crop2 = im2.crop((0.8*size2[0], 0, size2[0], size2[1]))
|
preview_crop2 = im2.crop((0.8*size2[0], 0, size2[0], size2[1]))
|
||||||
if low_quality_preview:
|
if low_quality_preview:
|
||||||
preview_crop2 = preview_crop2.convert('1', dither=Dither.NONE)
|
preview_crop2 = preview_crop2.convert('1', dither=Dither.NONE)
|
||||||
calculation_crop2 = im2.crop((0.96*size2[0], 0, .99* size2[0], size2[1])).convert('1', dither=Dither.NONE)
|
calculation_crop2 = im2.crop((0.96*size2[0], 0, .99* size2[0], size2[1])).convert('1', dither=Dither.NONE)
|
||||||
#crop2 = crop22
|
|
||||||
# dst = Image.new('1', (im1.width + im2.width, im1.height))
|
# dst = Image.new('1', (im1.width + im2.width, im1.height))
|
||||||
# dst.paste(im2, (0, 0))
|
# dst.paste(im2, (0, 0))
|
||||||
# dst.paste(im1, (im1.width, 0))
|
# dst.paste(im1, (im1.width, 0))
|
||||||
@@ -811,6 +827,91 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
# noinspection PyCallByClass
|
# noinspection PyCallByClass
|
||||||
QDesktopServices.openUrl(QUrl('https://humblebundleinc.sjv.io/3JaR3A'))
|
QDesktopServices.openUrl(QUrl('https://humblebundleinc.sjv.io/3JaR3A'))
|
||||||
|
|
||||||
|
def toggleExpert(self):
|
||||||
|
# noinspection PyCallByClass
|
||||||
|
if self.expertMode:
|
||||||
|
self.hide_expert_options()
|
||||||
|
self.expertMode = False
|
||||||
|
else:
|
||||||
|
self.show_expert_options()
|
||||||
|
self.expertMode = True
|
||||||
|
|
||||||
|
def show_expert_options(self):
|
||||||
|
GUI.spreadShiftBox.show()
|
||||||
|
GUI.fileFusionBox.show()
|
||||||
|
GUI.jpegQualityBox.show()
|
||||||
|
GUI.pngLegacyBox.show()
|
||||||
|
GUI.webpBox.show()
|
||||||
|
GUI.lightnovelBox.show()
|
||||||
|
GUI.wallpaperBox.show()
|
||||||
|
GUI.outputSplit.show()
|
||||||
|
GUI.maximizeStrips.show()
|
||||||
|
GUI.noRotateBox.show()
|
||||||
|
GUI.rotateFirstBox.show()
|
||||||
|
GUI.coverFillBox.show()
|
||||||
|
GUI.rotateRightBox.show()
|
||||||
|
GUI.smartCoverCropBox.show()
|
||||||
|
GUI.onePageLandscapeBox.show()
|
||||||
|
GUI.qualityBox.show()
|
||||||
|
GUI.gammaBox.show()
|
||||||
|
GUI.interPanelCropBox.show()
|
||||||
|
GUI.autoLevelBox.show()
|
||||||
|
GUI.autocontrastBox.show()
|
||||||
|
GUI.vertical4PanelBox.show()
|
||||||
|
GUI.tempDirBox.show()
|
||||||
|
GUI.deleteBox.show()
|
||||||
|
GUI.metadataTitleBox.show()
|
||||||
|
GUI.keepComicInfoBox.show()
|
||||||
|
GUI.pdfWidthBox.show()
|
||||||
|
GUI.chunkSizeCheckBox.show()
|
||||||
|
GUI.disableProcessingBox.show()
|
||||||
|
GUI.noQuantizeBox.show()
|
||||||
|
GUI.forcePngRgbBox.show()
|
||||||
|
GUI.invertDirectionBox.show()
|
||||||
|
GUI.ebokBox.show()
|
||||||
|
GUI.languageEdit.show()
|
||||||
|
GUI.labelSpreadsButton.show()
|
||||||
|
GUI.editorButton.show()
|
||||||
|
GUI.easyLabel.hide()
|
||||||
|
|
||||||
|
def hide_expert_options(self):
|
||||||
|
GUI.spreadShiftBox.hide()
|
||||||
|
GUI.fileFusionBox.hide()
|
||||||
|
GUI.jpegQualityBox.hide()
|
||||||
|
GUI.pngLegacyBox.hide()
|
||||||
|
GUI.webpBox.hide()
|
||||||
|
GUI.lightnovelBox.hide()
|
||||||
|
GUI.wallpaperBox.hide()
|
||||||
|
GUI.outputSplit.hide()
|
||||||
|
GUI.maximizeStrips.hide()
|
||||||
|
GUI.noRotateBox.hide()
|
||||||
|
GUI.rotateFirstBox.hide()
|
||||||
|
GUI.coverFillBox.hide()
|
||||||
|
GUI.rotateRightBox.hide()
|
||||||
|
GUI.smartCoverCropBox.hide()
|
||||||
|
GUI.onePageLandscapeBox.hide()
|
||||||
|
GUI.qualityBox.hide()
|
||||||
|
GUI.gammaBox.hide()
|
||||||
|
GUI.interPanelCropBox.hide()
|
||||||
|
GUI.autoLevelBox.hide()
|
||||||
|
GUI.autocontrastBox.hide()
|
||||||
|
GUI.vertical4PanelBox.hide()
|
||||||
|
GUI.tempDirBox.hide()
|
||||||
|
GUI.deleteBox.hide()
|
||||||
|
GUI.metadataTitleBox.hide()
|
||||||
|
GUI.keepComicInfoBox.hide()
|
||||||
|
GUI.pdfWidthBox.hide()
|
||||||
|
GUI.chunkSizeCheckBox.hide()
|
||||||
|
GUI.disableProcessingBox.hide()
|
||||||
|
GUI.noQuantizeBox.hide()
|
||||||
|
GUI.forcePngRgbBox.hide()
|
||||||
|
GUI.invertDirectionBox.hide()
|
||||||
|
GUI.ebokBox.hide()
|
||||||
|
GUI.languageEdit.hide()
|
||||||
|
GUI.labelSpreadsButton.hide()
|
||||||
|
GUI.editorButton.hide()
|
||||||
|
GUI.easyLabel.show()
|
||||||
|
|
||||||
def openYouTube(self):
|
def openYouTube(self):
|
||||||
# noinspection PyCallByClass
|
# noinspection PyCallByClass
|
||||||
QDesktopServices.openUrl(QUrl('https://www.youtube.com/@eink-dude'))
|
QDesktopServices.openUrl(QUrl('https://www.youtube.com/@eink-dude'))
|
||||||
@@ -959,6 +1060,14 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
GUI.noQuantizeBox.setEnabled(False)
|
GUI.noQuantizeBox.setEnabled(False)
|
||||||
GUI.forcePngRgbBox.setEnabled(False)
|
GUI.forcePngRgbBox.setEnabled(False)
|
||||||
|
|
||||||
|
def toggleWallpaperBox(self, value):
|
||||||
|
if value == 2:
|
||||||
|
GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked)
|
||||||
|
GUI.pngLegacyBox.setChecked(True)
|
||||||
|
GUI.rotateBox.setChecked(True)
|
||||||
|
GUI.noRotateBox.setChecked(True)
|
||||||
|
GUI.upscaleBox.setChecked(True)
|
||||||
|
self.changeFormat(3)
|
||||||
|
|
||||||
def togglechunkSizeCheckBox(self, value):
|
def togglechunkSizeCheckBox(self, value):
|
||||||
GUI.chunkSizeWidget.setVisible(value)
|
GUI.chunkSizeWidget.setVisible(value)
|
||||||
@@ -1058,9 +1167,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
GUI.upscaleBox.setChecked(True)
|
GUI.upscaleBox.setChecked(True)
|
||||||
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():
|
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'FOLDER', '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')
|
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
|
||||||
GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
|
GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
|
||||||
|
GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked)
|
||||||
else:
|
else:
|
||||||
GUI.borderBox.setCheckState(Qt.CheckState.Unchecked)
|
GUI.borderBox.setCheckState(Qt.CheckState.Unchecked)
|
||||||
|
|
||||||
@@ -1167,14 +1277,17 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
if not GUI.convertButton.isEnabled():
|
if not GUI.convertButton.isEnabled():
|
||||||
event.ignore()
|
event.ignore()
|
||||||
self.settings.setValue('settingsVersion', __version__)
|
self.settings.setValue('settingsVersion', __version__)
|
||||||
|
self.settings.setValue('expertMode', self.expertMode)
|
||||||
self.settings.setValue('lastPath', self.lastPath)
|
self.settings.setValue('lastPath', self.lastPath)
|
||||||
self.settings.setValue('defaultOutputFolder', self.defaultOutputFolder)
|
self.settings.setValue('defaultOutputFolder', self.defaultOutputFolder)
|
||||||
self.settings.setValue('lastDevice', GUI.deviceBox.currentIndex())
|
self.settings.setValue('lastDevice', GUI.deviceBox.currentIndex())
|
||||||
self.settings.setValue('currentFormat', GUI.formatBox.currentIndex())
|
self.settings.setValue('currentFormat', GUI.formatBox.currentIndex())
|
||||||
self.settings.setValue('startNumber', self.startNumber + 1)
|
self.settings.setValue('startNumber', self.startNumber + 1)
|
||||||
|
self.settings.setValue('startNumber2', self.startNumber2 + 1)
|
||||||
self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height()))
|
self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height()))
|
||||||
self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(),
|
self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(),
|
||||||
'lightnovelBox': GUI.lightnovelBox.checkState(),
|
'lightnovelBox': GUI.lightnovelBox.checkState(),
|
||||||
|
'wallpaperBox': GUI.wallpaperBox.checkState(),
|
||||||
'ebokBox': GUI.ebokBox.checkState(),
|
'ebokBox': GUI.ebokBox.checkState(),
|
||||||
'invertDirectionBox': GUI.invertDirectionBox.checkState(),
|
'invertDirectionBox': GUI.invertDirectionBox.checkState(),
|
||||||
'languageEdit': GUI.languageEdit.text(),
|
'languageEdit': GUI.languageEdit.text(),
|
||||||
@@ -1310,11 +1423,16 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
if not os.path.exists(self.defaultOutputFolder):
|
if not os.path.exists(self.defaultOutputFolder):
|
||||||
self.defaultOutputFolder = ''
|
self.defaultOutputFolder = ''
|
||||||
|
|
||||||
|
self.expertMode = self.settings.value('expertMode', False, type=bool)
|
||||||
|
if not self.expertMode:
|
||||||
|
GUI.hide_expert_options()
|
||||||
|
|
||||||
# default is Kindle Paperwhite 12th Gen
|
# default is Kindle Paperwhite 12th Gen
|
||||||
self.lastDevice = self.settings.value('lastDevice', 3, type=int)
|
self.lastDevice = self.settings.value('lastDevice', 3, type=int)
|
||||||
|
|
||||||
self.currentFormat = self.settings.value('currentFormat', 0, type=int)
|
self.currentFormat = self.settings.value('currentFormat', 0, type=int)
|
||||||
self.startNumber = self.settings.value('startNumber', 0, type=int)
|
self.startNumber = self.settings.value('startNumber', 0, type=int)
|
||||||
|
self.startNumber2 = self.settings.value('startNumber2', 0, type=int)
|
||||||
self.windowSize = self.settings.value('windowSize', '0x0', type=str)
|
self.windowSize = self.settings.value('windowSize', '0x0', type=str)
|
||||||
default_options = {'gammaSlider': 0, 'croppingBox': 2, 'croppingPowerSlider': 100, 'rotateBox': 1, 'mangaBox': 2}
|
default_options = {'gammaSlider': 0, 'croppingBox': 2, 'croppingPowerSlider': 100, 'rotateBox': 1, 'mangaBox': 2}
|
||||||
try:
|
try:
|
||||||
@@ -1322,7 +1440,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
except Exception:
|
except Exception:
|
||||||
self.options = default_options
|
self.options = default_options
|
||||||
self.worker = WorkerThread()
|
self.worker = WorkerThread()
|
||||||
self.versionCheck = VersionThread()
|
self.versionCheck = VersionThread(self.startNumber2)
|
||||||
self.progress = ProgressThread()
|
self.progress = ProgressThread()
|
||||||
self.tray = SystemTrayIcon()
|
self.tray = SystemTrayIcon()
|
||||||
self.conversionAlive = False
|
self.conversionAlive = False
|
||||||
@@ -1356,6 +1474,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
"MOBI/AZW3": {'icon': 'MOBI', 'format': 'MOBI'},
|
"MOBI/AZW3": {'icon': 'MOBI', 'format': 'MOBI'},
|
||||||
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
|
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
|
||||||
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
|
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
|
||||||
|
"Folder of images": {'icon': 'FOLDER', 'format': 'FOLDER'},
|
||||||
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
|
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
|
||||||
"PDF (200MB limit)": {'icon': 'EPUB', 'format': 'PDF-200MB'},
|
"PDF (200MB limit)": {'icon': 'EPUB', 'format': 'PDF-200MB'},
|
||||||
"KFX (Send to Kindle EPUB)": {'icon': 'KFX', 'format': 'KFX'},
|
"KFX (Send to Kindle EPUB)": {'icon': 'KFX', 'format': 'KFX'},
|
||||||
@@ -1539,8 +1658,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
statusBarLabel.setOpenExternalLinks(True)
|
statusBarLabel.setOpenExternalLinks(True)
|
||||||
GUI.statusBar.addPermanentWidget(statusBarLabel, 1)
|
GUI.statusBar.addPermanentWidget(statusBarLabel, 1)
|
||||||
|
|
||||||
self.addMessage('<b>Tip:</b> Hover mouse over options to see additional information in tooltips.', 'info')
|
self.addMessage('<b>Tip:</b> Hover mouse over options/buttons to see explanations. Boxes can be partially/fully checked.', 'info')
|
||||||
self.addMessage('<b>Tip:</b> You can drag and drop image folders or comic files/archives into this window to convert.', 'info')
|
self.addMessage('<b>Tip:</b> You can drag and drop image folders or comic files/archives into this window to convert.', 'info')
|
||||||
|
self.addMessage("<b>Tip:</b> Calibre may add margins! USB drop directly into the device's documents folder instead.", 'info')
|
||||||
|
self.addMessage("<b>Tip:</b> You can toggle easy/expert mode using button at top right.", 'info')
|
||||||
if self.startNumber < 5:
|
if self.startNumber < 5:
|
||||||
self.addMessage('Since you are a new user of <b>KCC</b> please see few '
|
self.addMessage('Since you are a new user of <b>KCC</b> please see few '
|
||||||
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
||||||
@@ -1561,6 +1682,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
|
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
|
||||||
GUI.kofiButton.clicked.connect(self.openKofi)
|
GUI.kofiButton.clicked.connect(self.openKofi)
|
||||||
GUI.humbleButton.clicked.connect(self.openHumble)
|
GUI.humbleButton.clicked.connect(self.openHumble)
|
||||||
|
GUI.expertButton.clicked.connect(self.toggleExpert)
|
||||||
GUI.convertButton.clicked.connect(self.convertStart)
|
GUI.convertButton.clicked.connect(self.convertStart)
|
||||||
GUI.labelSpreadsButton.clicked.connect(self.labelSpreadsStart)
|
GUI.labelSpreadsButton.clicked.connect(self.labelSpreadsStart)
|
||||||
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
|
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
|
||||||
@@ -1571,6 +1693,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox)
|
GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox)
|
||||||
GUI.qualityBox.stateChanged.connect(self.togglequalityBox)
|
GUI.qualityBox.stateChanged.connect(self.togglequalityBox)
|
||||||
GUI.mozJpegBox.stateChanged.connect(self.toggleImageFormatBox)
|
GUI.mozJpegBox.stateChanged.connect(self.toggleImageFormatBox)
|
||||||
|
GUI.wallpaperBox.stateChanged.connect(self.toggleWallpaperBox)
|
||||||
GUI.chunkSizeCheckBox.stateChanged.connect(self.togglechunkSizeCheckBox)
|
GUI.chunkSizeCheckBox.stateChanged.connect(self.togglechunkSizeCheckBox)
|
||||||
GUI.deviceBox.activated.connect(self.changeDevice)
|
GUI.deviceBox.activated.connect(self.changeDevice)
|
||||||
GUI.formatBox.activated.connect(self.changeFormat)
|
GUI.formatBox.activated.connect(self.changeFormat)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '11.0.2'
|
__version__ = '11.2.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'
|
||||||
|
|||||||
@@ -892,6 +892,13 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_
|
|||||||
|
|
||||||
cpu = cpu_count()
|
cpu = cpu_count()
|
||||||
|
|
||||||
|
if not render:
|
||||||
|
print('PDF input can also use legacy extract option if you have any problems')
|
||||||
|
if GUI:
|
||||||
|
GUI.addMessage.emit('PDF input can also use legacy extract option if you have any problems'
|
||||||
|
, 'info', False)
|
||||||
|
GUI.addMessage.emit('', '', False)
|
||||||
|
|
||||||
# make vectors of arguments for the processes
|
# make vectors of arguments for the processes
|
||||||
vectors = [(i, cpu, filename, output_dir, target_width, target_height, pdfwidth) for i in range(cpu)]
|
vectors = [(i, cpu, filename, output_dir, target_width, target_height, pdfwidth) for i in range(cpu)]
|
||||||
print("Starting %i processes for '%s'." % (cpu, filename))
|
print("Starting %i processes for '%s'." % (cpu, filename))
|
||||||
@@ -1058,6 +1065,8 @@ def getWorkFolder(afile, options, workdir=None):
|
|||||||
|
|
||||||
|
|
||||||
def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
||||||
|
if options.folder_output:
|
||||||
|
ext = ''
|
||||||
source_path = Path(srcpath)
|
source_path = Path(srcpath)
|
||||||
if srcpath[-1] == os.path.sep:
|
if srcpath[-1] == os.path.sep:
|
||||||
srcpath = srcpath[:-1]
|
srcpath = srcpath[:-1]
|
||||||
@@ -1069,7 +1078,7 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
|||||||
ext = '.kepub.epub'
|
ext = '.kepub.epub'
|
||||||
if wantedname is not None:
|
if wantedname is not None:
|
||||||
wanted_root, wanted_ext = os.path.splitext(wantedname)
|
wanted_root, wanted_ext = os.path.splitext(wantedname)
|
||||||
if wantedname.endswith(ext):
|
if not options.folder_output and wantedname.endswith(ext):
|
||||||
filename = os.path.abspath(wantedname)
|
filename = os.path.abspath(wantedname)
|
||||||
elif wanted_ext == '.mobi' and ext == '.epub':
|
elif wanted_ext == '.mobi' and ext == '.epub':
|
||||||
filename = os.path.abspath(wanted_root + ext)
|
filename = os.path.abspath(wanted_root + ext)
|
||||||
@@ -1093,10 +1102,10 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
|||||||
filename = source_path.with_name(name)
|
filename = source_path.with_name(name)
|
||||||
else:
|
else:
|
||||||
filename = os.path.splitext(srcpath)[0] + tomenumber + ext
|
filename = os.path.splitext(srcpath)[0] + tomenumber + ext
|
||||||
if os.path.isfile(filename):
|
if os.path.exists(filename):
|
||||||
counter = 0
|
counter = 0
|
||||||
basename = os.path.splitext(filename)[0]
|
basename = os.path.splitext(filename)[0]
|
||||||
while os.path.isfile(basename + '_kcc' + str(counter) + ext):
|
while os.path.exists(basename + '_kcc' + str(counter) + ext):
|
||||||
counter += 1
|
counter += 1
|
||||||
filename = basename + '_kcc' + str(counter) + ext
|
filename = basename + '_kcc' + str(counter) + ext
|
||||||
elif options.format == 'MOBI' and ext == '.epub':
|
elif options.format == 'MOBI' and ext == '.epub':
|
||||||
@@ -1430,6 +1439,9 @@ def slugify(value, options, is_natural_sorted):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def makeZIP(zipfilename, basedir, job_progress='', isepub=False):
|
def makeZIP(zipfilename, basedir, job_progress='', isepub=False):
|
||||||
|
if options.folder_output:
|
||||||
|
move(basedir, zipfilename)
|
||||||
|
return zipfilename
|
||||||
start = perf_counter()
|
start = perf_counter()
|
||||||
if SEVENZIP in available_archive_tools():
|
if SEVENZIP in available_archive_tools():
|
||||||
if isepub:
|
if isepub:
|
||||||
@@ -1475,6 +1487,8 @@ def makeParser():
|
|||||||
help="Manga style (right-to-left reading and splitting)")
|
help="Manga style (right-to-left reading and splitting)")
|
||||||
main_options.add_argument("--lightnovel", action="store_true", dest="lightnovel", default=False,
|
main_options.add_argument("--lightnovel", action="store_true", dest="lightnovel", default=False,
|
||||||
help="Only resize images and preserve original file structure.")
|
help="Only resize images and preserve original file structure.")
|
||||||
|
main_options.add_argument("--wallpaper", action="store_true", dest="wallpaper", default=False,
|
||||||
|
help="Crop to fill screen.")
|
||||||
main_options.add_argument("--ebok", action="store_true", dest="ebok", default=False,
|
main_options.add_argument("--ebok", action="store_true", dest="ebok", default=False,
|
||||||
help="Force EBOK tag instead of PDOC for MOBI")
|
help="Force EBOK tag instead of PDOC for MOBI")
|
||||||
main_options.add_argument("--invertdirection", action="store_true", dest="invertdirection", default=False,
|
main_options.add_argument("--invertdirection", action="store_true", dest="invertdirection", default=False,
|
||||||
@@ -1639,6 +1653,12 @@ def checkOptions(options):
|
|||||||
options.keep_epub = True
|
options.keep_epub = True
|
||||||
options.format = 'MOBI'
|
options.format = 'MOBI'
|
||||||
options.kfx = False
|
options.kfx = False
|
||||||
|
options.skip_zip = False
|
||||||
|
options.folder_output = False
|
||||||
|
if options.format == 'FOLDER':
|
||||||
|
options.format = 'CBZ'
|
||||||
|
options.skip_zip = True
|
||||||
|
options.folder_output = True
|
||||||
if options.format == 'Auto':
|
if options.format == 'Auto':
|
||||||
if options.profile in ['KDX']:
|
if options.profile in ['KDX']:
|
||||||
options.format = 'CBZ'
|
options.format = 'CBZ'
|
||||||
@@ -1811,6 +1831,7 @@ def makeBook(source, qtgui=None, job_progress=''):
|
|||||||
print(f"{job_progress}Preparing source images...")
|
print(f"{job_progress}Preparing source images...")
|
||||||
path = getWorkFolder(source, options)
|
path = getWorkFolder(source, options)
|
||||||
print(f"{job_progress}Checking images...")
|
print(f"{job_progress}Checking images...")
|
||||||
|
_, ext = os.path.splitext(source)
|
||||||
|
|
||||||
if options.lightnovel:
|
if options.lightnovel:
|
||||||
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||||
@@ -1831,7 +1852,6 @@ def makeBook(source, qtgui=None, job_progress=''):
|
|||||||
if img.size[0] > x or img.size[1] > y:
|
if img.size[0] > x or img.size[1] > y:
|
||||||
img = ImageOps.contain(img, (x, y))
|
img = ImageOps.contain(img, (x, y))
|
||||||
img.save(os.path.join(root, file), quality=options.jpegquality)
|
img.save(os.path.join(root, file), quality=options.jpegquality)
|
||||||
_, ext = os.path.splitext(source)
|
|
||||||
if ext != '.epub':
|
if ext != '.epub':
|
||||||
ext = '.cbz'
|
ext = '.cbz'
|
||||||
output_file = getOutputFilename(source, options.output, ext, '')
|
output_file = getOutputFilename(source, options.output, ext, '')
|
||||||
@@ -1859,6 +1879,8 @@ def makeBook(source, qtgui=None, job_progress=''):
|
|||||||
if i in data['spreads']:
|
if i in data['spreads']:
|
||||||
im1 = Image.open(os.path.join(root, sorted_files[i]))
|
im1 = Image.open(os.path.join(root, sorted_files[i]))
|
||||||
im2 = Image.open(os.path.join(root, sorted_files[i+1]))
|
im2 = Image.open(os.path.join(root, sorted_files[i+1]))
|
||||||
|
if not options.righttoleft:
|
||||||
|
im1, im2 = im2, im1
|
||||||
dst = Image.new('RGB', (im1.width + im2.width, im1.height))
|
dst = Image.new('RGB', (im1.width + im2.width, im1.height))
|
||||||
dst.paste(im2, (0, 0))
|
dst.paste(im2, (0, 0))
|
||||||
dst.paste(im1, (im1.width, 0))
|
dst.paste(im1, (im1.width, 0))
|
||||||
@@ -1872,6 +1894,25 @@ def makeBook(source, qtgui=None, job_progress=''):
|
|||||||
if options.filefusion:
|
if options.filefusion:
|
||||||
# Strip the fusion_0001_ sort prefix from makeFusion if present
|
# Strip the fusion_0001_ sort prefix from makeFusion if present
|
||||||
chapterNames = {k: sub(r'^fusion_\d{4}_', '', v) for k, v in chapterNames.items()}
|
chapterNames = {k: sub(r'^fusion_\d{4}_', '', v) for k, v in chapterNames.items()}
|
||||||
|
|
||||||
|
source_path = Path(source)
|
||||||
|
options.customcover = False
|
||||||
|
if source_path.parent.joinpath('Covers').is_dir():
|
||||||
|
series = os_sorted(filter(lambda s: s.endswith(ext) and '_kcc' not in s, os.listdir(source_path.parent)))
|
||||||
|
source_index = series.index(os.path.basename(source))
|
||||||
|
covers = os.listdir(source_path.parent.joinpath('Covers'))
|
||||||
|
filtered_covers = []
|
||||||
|
for cover in covers:
|
||||||
|
_, cover_ext = getImageFileName(cover)
|
||||||
|
if cover_ext in IMAGE_TYPES:
|
||||||
|
filtered_covers.append(cover)
|
||||||
|
sorted_covers = os_sorted(filtered_covers)
|
||||||
|
try:
|
||||||
|
cover_path = source_path.parent.joinpath('Covers', sorted_covers[source_index])
|
||||||
|
options.customcover = True
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
cover = None
|
cover = None
|
||||||
if not options.webtoon:
|
if not options.webtoon:
|
||||||
cover = image.Cover(cover_path, options)
|
cover = image.Cover(cover_path, options)
|
||||||
@@ -1953,7 +1994,7 @@ def makeBook(source, qtgui=None, job_progress=''):
|
|||||||
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
|
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
|
||||||
else:
|
else:
|
||||||
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
|
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
|
||||||
if cover and cover.smartcover:
|
if cover and (cover.smartcover or options.customcover):
|
||||||
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', '##cover.jpg'), tomeNumber, len(tomes))
|
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', '##cover.jpg'), tomeNumber, len(tomes))
|
||||||
if options.comicinfo_xml:
|
if options.comicinfo_xml:
|
||||||
with open(os.path.join(tome, 'OEBPS', 'Images', 'ComicInfo.xml'), 'wb') as xmlOutput:
|
with open(os.path.join(tome, 'OEBPS', 'Images', 'ComicInfo.xml'), 'wb') as xmlOutput:
|
||||||
@@ -1964,7 +2005,7 @@ def makeBook(source, qtgui=None, job_progress=''):
|
|||||||
# determine output filename based on source and tome count
|
# determine output filename based on source and tome count
|
||||||
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
|
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
|
||||||
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
|
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
|
||||||
if cover and cover.smartcover:
|
if cover and (cover.smartcover or options.customcover):
|
||||||
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
|
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
|
||||||
# use optimized buildPDF logic with streaming and compression
|
# use optimized buildPDF logic with streaming and compression
|
||||||
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file)
|
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file)
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ def splitImage(work):
|
|||||||
imgProcess = imgEdges.point(lambda p: 255 if p > 6 else 0).convert('1', dither=Dither.NONE)
|
imgProcess = imgEdges.point(lambda p: 255 if p > 6 else 0).convert('1', dither=Dither.NONE)
|
||||||
|
|
||||||
widthImg, heightImg = imgOrg.size
|
widthImg, heightImg = imgOrg.size
|
||||||
|
if widthImg < 300:
|
||||||
|
raise UserWarning('PDF render failed < 300 px. Try legacy extract option instead.')
|
||||||
if heightImg > opt.height:
|
if heightImg > opt.height:
|
||||||
if opt.debug:
|
if opt.debug:
|
||||||
drawImg = Image.open(filePath).convert(mode='RGBA')
|
drawImg = Image.open(filePath).convert(mode='RGBA')
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class ComicArchive:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if missing:
|
if missing:
|
||||||
raise OSError(f'Extraction failed, install <a href="https://github.com/ciromattia/kcc#7-zip">specialized extraction software.</a> ')
|
raise UserWarning(f'Extraction failed, install <a href="https://github.com/ciromattia/kcc#7-zip">specialized extraction software.</a> ')
|
||||||
else:
|
else:
|
||||||
raise OSError(EXTRACTION_ERROR)
|
raise OSError(EXTRACTION_ERROR)
|
||||||
|
|
||||||
|
|||||||
@@ -513,7 +513,9 @@ class ComicPage:
|
|||||||
self.image = erase_rainbow_artifacts(self.image, is_color)
|
self.image = erase_rainbow_artifacts(self.image, is_color)
|
||||||
|
|
||||||
def resizeImage(self):
|
def resizeImage(self):
|
||||||
if self.opt.norotate and self.targetPathOrder in ('-kcc-a', '-kcc-d') and not self.opt.kindle_scribe_azw3:
|
if self.opt.wallpaper:
|
||||||
|
pass
|
||||||
|
elif self.opt.norotate and self.targetPathOrder in ('-kcc-a', '-kcc-d') and not self.opt.kindle_scribe_azw3:
|
||||||
# TODO: Kindle Scribe case
|
# TODO: Kindle Scribe case
|
||||||
if self.opt.kindle_azw3 and any(dim > 1920 for dim in self.image.size):
|
if self.opt.kindle_azw3 and any(dim > 1920 for dim in self.image.size):
|
||||||
self.image = ImageOps.contain(self.image, (1920, 1920), Image.Resampling.LANCZOS)
|
self.image = ImageOps.contain(self.image, (1920, 1920), Image.Resampling.LANCZOS)
|
||||||
@@ -536,6 +538,8 @@ class ComicPage:
|
|||||||
self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill)
|
self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill)
|
||||||
elif self.opt.stretch:
|
elif self.opt.stretch:
|
||||||
self.image = self.image.resize(self.size, method)
|
self.image = self.image.resize(self.size, method)
|
||||||
|
elif self.opt.wallpaper:
|
||||||
|
self.image = ImageOps.fit(self.image, self.size, method=method)
|
||||||
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
||||||
pass
|
pass
|
||||||
else: # if image bigger than device resolution or smaller with upscaling
|
else: # if image bigger than device resolution or smaller with upscaling
|
||||||
|
|||||||