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

|
||||||
|
|
||||||
### A word of warning
|
### A word of warning
|
||||||
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
||||||
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
||||||
@@ -31,6 +33,9 @@ If you find **KCC** valuable you can consider donating to the authors:
|
|||||||
- Alex Xu (active 2023-Present)
|
- Alex Xu (active 2023-Present)
|
||||||
- [](https://www.paypal.com/donate/?business=QFJVE7A6LCP6U&no_recurring=0&item_name=Kindle+Comic+Converter¤cy_code=USD)
|
- [](https://www.paypal.com/donate/?business=QFJVE7A6LCP6U&no_recurring=0&item_name=Kindle+Comic+Converter¤cy_code=USD)
|
||||||
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
|
- Free code signing on Windows provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
|
||||||
|
|
||||||
## DOWNLOADS
|
## DOWNLOADS
|
||||||
|
|
||||||
@@ -68,9 +73,11 @@ If you have issues detecting it, get stuck on the MOBI conversion step, or use L
|
|||||||
|
|
||||||
### 7-Zip
|
### 7-Zip
|
||||||
|
|
||||||
This is only required for certain files and advanced features.
|
This is optional but will make conversions much faster.
|
||||||
|
|
||||||
KCC will ask you to install if needed.
|
This is required for certain files and advanced features.
|
||||||
|
|
||||||
|
KCC will ask you to install if needed.
|
||||||
|
|
||||||
Refer to the wiki to install: https://github.com/ciromattia/kcc/wiki/Installation#7-zip
|
Refer to the wiki to install: https://github.com/ciromattia/kcc/wiki/Installation#7-zip
|
||||||
|
|
||||||
@@ -317,5 +324,5 @@ The app relies and includes the following scripts:
|
|||||||
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||||
|
|
||||||
## COPYRIGHT
|
## COPYRIGHT
|
||||||
Copyright (c) 2012-2023 Ciro Mattia Gonano, Paweł Jastrzębski and Darodi.
|
Copyright (c) 2012-2025 Ciro Mattia Gonano, Paweł Jastrzębski, Darodi and Alex Xu.
|
||||||
**KCC** is released under ISC LICENSE; see [LICENSE.txt](./LICENSE.txt) for further details.
|
**KCC** is released under ISC LICENSE; see [LICENSE.txt](./LICENSE.txt) for further details.
|
||||||
|
|||||||
14
appveyor.yml
14
appveyor.yml
@@ -1,14 +0,0 @@
|
|||||||
environment:
|
|
||||||
PYTHON: "C:\\Python37-x64"
|
|
||||||
|
|
||||||
install:
|
|
||||||
- set PATH="%PYTHON%\\Scripts";%PATH%
|
|
||||||
- "%PYTHON%\\python.exe -m pip install --upgrade pip setuptools wheel"
|
|
||||||
- "%PYTHON%\\python.exe -m pip install -r requirements.txt"
|
|
||||||
- "%PYTHON%\\python.exe -m pip install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip"
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- "%PYTHON%\\python.exe setup.py build_binary"
|
|
||||||
|
|
||||||
artifacts:
|
|
||||||
- path: dist\KCC*
|
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
<item row="5" column="2">
|
<item row="5" column="2">
|
||||||
<widget class="QCheckBox" name="disableProcessingBox">
|
<widget class="QCheckBox" name="disableProcessingBox">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><pre style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Do not process any image, ignore profile and processing options</pre></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Disable processing</string>
|
<string>Disable processing</string>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ from packaging.version import Version
|
|||||||
from raven import Client
|
from raven import Client
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
|
|
||||||
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run
|
from .shared import HTMLStripper, available_archive_tools, sanitizeTrace, walkLevel, subprocess_run
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from . import comic2ebook
|
from . import comic2ebook
|
||||||
from . import metadata
|
from . import metadata
|
||||||
@@ -317,13 +317,8 @@ class WorkerThread(QThread):
|
|||||||
GUI.progress.content = ''
|
GUI.progress.content = ''
|
||||||
self.errors = True
|
self.errors = True
|
||||||
_, _, traceback = sys.exc_info()
|
_, _, traceback = sys.exc_info()
|
||||||
if len(err.args) == 1:
|
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
||||||
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
|
||||||
else:
|
|
||||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
|
||||||
% (jobargv[-1], str(err.args[0]), err.args[1]), 'error')
|
|
||||||
GUI.sentry.extra_context({'realTraceback': err.args[1]})
|
|
||||||
if ' is corrupted.' not in str(err):
|
if ' is corrupted.' not in str(err):
|
||||||
GUI.sentry.captureException()
|
GUI.sentry.captureException()
|
||||||
MW.addMessage.emit('Error during conversion! Please consult '
|
MW.addMessage.emit('Error during conversion! Please consult '
|
||||||
@@ -1071,19 +1066,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
self.addMessage('Since you are a new user of <b>KCC</b> please see few '
|
self.addMessage('Since you are a new user of <b>KCC</b> please see few '
|
||||||
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
||||||
'info')
|
'info')
|
||||||
try:
|
|
||||||
subprocess_run(['tar'], stdout=PIPE, stderr=STDOUT)
|
self.tar = 'tar' in available_archive_tools()
|
||||||
self.tar = True
|
self.sevenzip = '7z' in available_archive_tools()
|
||||||
except FileNotFoundError:
|
if not any([self.tar, self.sevenzip]):
|
||||||
self.tar = False
|
self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
|
||||||
try:
|
' to enable CBZ/CBR/ZIP/etc processing.', 'warning')
|
||||||
subprocess_run(['7z'], stdout=PIPE, stderr=STDOUT)
|
|
||||||
self.sevenzip = True
|
|
||||||
except FileNotFoundError:
|
|
||||||
self.sevenzip = False
|
|
||||||
if not self.tar:
|
|
||||||
self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
|
|
||||||
' to enable CBZ/CBR/ZIP/etc processing.', 'warning')
|
|
||||||
self.detectKindleGen(True)
|
self.detectKindleGen(True)
|
||||||
|
|
||||||
APP.messageFromOtherInstance.connect(self.handleMessage)
|
APP.messageFromOtherInstance.connect(self.handleMessage)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Resource object code (Python 3)
|
# Resource object code (Python 3)
|
||||||
# Created by: object code
|
# Created by: object code
|
||||||
# Created by: The Resource Compiler for Qt version 6.8.1
|
# Created by: The Resource Compiler for Qt version 6.8.2
|
||||||
# WARNING! All changes made in this file will be lost!
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
from PySide6 import QtCore
|
from PySide6 import QtCore
|
||||||
@@ -11612,51 +11612,51 @@ qt_resource_struct = b"\
|
|||||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x07\
|
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x07\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x02.\xed\
|
\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x02.\xed\
|
||||||
\x00\x00\x01\x88;p\xbcB\
|
\x00\x00\x01\x95\x8bV),\
|
||||||
\x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x02\x83\x87\
|
\x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x02\x83\x87\
|
||||||
\x00\x00\x01\x88;p\xbcB\
|
\x00\x00\x01\x95\x8bV)&\
|
||||||
\x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x02Y\x8c\
|
\x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x02Y\x8c\
|
||||||
\x00\x00\x01\x88;p\xbcB\
|
\x00\x00\x01\x95\x8bV)&\
|
||||||
\x00\x00\x01\xd6\x00\x00\x00\x00\x00\x01\x00\x02N)\
|
\x00\x00\x01\xd6\x00\x00\x00\x00\x00\x01\x00\x02N)\
|
||||||
\x00\x00\x01\x89\x89D9.\
|
\x00\x00\x01\x95\x8bV),\
|
||||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x0c\
|
\x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x0c\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x01(\x97\
|
\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x01(\x97\
|
||||||
\x00\x00\x01\x88;p\xbcB\
|
\x00\x00\x01\x95\x8bV),\
|
||||||
\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x011\xef\
|
\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x011\xef\
|
||||||
\x00\x00\x01\x94\x1a\xa2\xa2\x92\
|
\x00\x00\x01\x95\x8bV)5\
|
||||||
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x01\x1d\x90\
|
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x01\x1d\x90\
|
||||||
\x00\x00\x01\x88;p\xbcB\
|
\x00\x00\x01\x95\x8bV),\
|
||||||
\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x01:\x05\
|
\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x01:\x05\
|
||||||
\x00\x00\x01\x88;p\xbcB\
|
\x00\x00\x01\x95\x8bV)4\
|
||||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x03\x00\x00\x00\x11\
|
\x00\x00\x00X\x00\x02\x00\x00\x00\x03\x00\x00\x00\x11\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x02B\x00\x00\x00\x00\x00\x01\x00\x02\xb5\xd3\
|
\x00\x00\x02B\x00\x00\x00\x00\x00\x01\x00\x02\xb5\xd3\
|
||||||
\x00\x00\x01\x88;p\xbcJ\
|
\x00\x00\x01\x95\x8bV)b\
|
||||||
\x00\x00\x02\x14\x00\x00\x00\x00\x00\x01\x00\x02\x9f\xd6\
|
\x00\x00\x02\x14\x00\x00\x00\x00\x00\x01\x00\x02\x9f\xd6\
|
||||||
\x00\x00\x01\x88;p\xbcI\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x02*\x00\x00\x00\x00\x00\x01\x00\x02\xa93\
|
\x00\x00\x02*\x00\x00\x00\x00\x00\x01\x00\x02\xa93\
|
||||||
\x00\x00\x01\x88;p\xbcI\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x07\x00\x00\x00\x15\
|
\x00\x00\x00X\x00\x02\x00\x00\x00\x07\x00\x00\x00\x15\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x01P\xb1\
|
\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x01P\xb1\
|
||||||
\x00\x00\x01\x88;p\xbcJ\
|
\x00\x00\x01\x95\x8bV)b\
|
||||||
\x00\x00\x012\x00\x00\x00\x00\x00\x01\x00\x01yY\
|
\x00\x00\x012\x00\x00\x00\x00\x00\x01\x00\x01yY\
|
||||||
\x00\x00\x01\x88;p\xbcI\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x01\xd2-\
|
\x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x01\xd2-\
|
||||||
\x00\x00\x01\x94\xb4\xd4\xf0a\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x01z\x00\x00\x00\x00\x00\x01\x00\x01\x8c\xe6\
|
\x00\x00\x01z\x00\x00\x00\x00\x00\x01\x00\x01\x8c\xe6\
|
||||||
\x00\x00\x01\x88;p\xbcH\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x01LR\
|
\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x01LR\
|
||||||
\x00\x00\x01\x88;p\xbcF\
|
\x00\x00\x01\x95\x8bV)@\
|
||||||
\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x01?\xe9\
|
\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x01?\xe9\
|
||||||
\x00\x00\x01\x88;p\xbcH\
|
\x00\x00\x01\x95\x8bV)Q\
|
||||||
\x00\x00\x01T\x00\x00\x00\x00\x00\x01\x00\x01\x82\xb0\
|
\x00\x00\x01T\x00\x00\x00\x00\x00\x01\x00\x01\x82\xb0\
|
||||||
\x00\x00\x01\x88;p\xbcH\
|
\x00\x00\x01\x95\x8bV)Y\
|
||||||
\x00\x00\x00X\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1d\
|
\x00\x00\x00X\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1d\
|
||||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||||
\x00\x00\x00h\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
\x00\x00\x00h\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||||
\x00\x00\x01\x88;p\xbcH\
|
\x00\x00\x01\x95\x8bV)Q\
|
||||||
"
|
"
|
||||||
|
|
||||||
def qInitResources():
|
def qInitResources():
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
################################################################################
|
################################################################################
|
||||||
## Form generated from reading UI file 'KCC.ui'
|
## Form generated from reading UI file 'KCC.ui'
|
||||||
##
|
##
|
||||||
## Created by: Qt User Interface Compiler version 6.8.1
|
## Created by: Qt User Interface Compiler version 6.8.2
|
||||||
##
|
##
|
||||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -424,7 +424,7 @@ 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.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><pre style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Do not process any image, ignore profile and processing options</pre></body></html>", None))
|
self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
|
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
################################################################################
|
################################################################################
|
||||||
## Form generated from reading UI file 'MetaEditor.ui'
|
## Form generated from reading UI file 'MetaEditor.ui'
|
||||||
##
|
##
|
||||||
## Created by: Qt User Interface Compiler version 6.8.1
|
## Created by: Qt User Interface Compiler version 6.8.2
|
||||||
##
|
##
|
||||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '7.2.2'
|
__version__ = '7.3.3'
|
||||||
__license__ = 'ISC'
|
__license__ = 'ISC'
|
||||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import pathlib
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from time import strftime, gmtime
|
from time import perf_counter, strftime, gmtime
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from glob import glob, escape
|
from glob import glob, escape
|
||||||
from re import sub
|
from re import sub
|
||||||
@@ -33,14 +33,14 @@ from tempfile import mkdtemp, gettempdir, TemporaryFile
|
|||||||
from shutil import move, copytree, rmtree, copyfile
|
from shutil import move, copytree, rmtree, copyfile
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from natsort import os_sorted
|
from natsort import os_sort_keygen
|
||||||
from slugify import slugify as slugify_ext
|
from slugify import slugify as slugify_ext
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
from subprocess import STDOUT, PIPE
|
from subprocess import STDOUT, PIPE
|
||||||
from psutil import virtual_memory, disk_usage
|
from psutil import virtual_memory, disk_usage
|
||||||
from html import escape as hescape
|
from html import escape as hescape
|
||||||
|
|
||||||
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
from .shared import available_archive_tools, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
||||||
from . import comic2panel
|
from . import comic2panel
|
||||||
from . import image
|
from . import image
|
||||||
from . import comicarchive
|
from . import comicarchive
|
||||||
@@ -51,7 +51,7 @@ from . import kindle
|
|||||||
from . import __version__
|
from . import __version__
|
||||||
|
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
OS_SORT_KEY = os_sort_keygen()
|
||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
global options
|
global options
|
||||||
@@ -78,14 +78,14 @@ def main(argv=None):
|
|||||||
|
|
||||||
|
|
||||||
def buildHTML(path, imgfile, imgfilepath):
|
def buildHTML(path, imgfile, imgfilepath):
|
||||||
imgfilepath = md5Checksum(imgfilepath)
|
key = pathlib.Path(imgfilepath).name
|
||||||
filename = getImageFileName(imgfile)
|
filename = getImageFileName(imgfile)
|
||||||
deviceres = options.profileData[1]
|
deviceres = options.profileData[1]
|
||||||
if not options.noprocessing and "Rotated" in options.imgMetadata[imgfilepath]:
|
if not options.noprocessing and "Rotated" in options.imgMetadata[key]:
|
||||||
rotatedPage = True
|
rotatedPage = True
|
||||||
else:
|
else:
|
||||||
rotatedPage = False
|
rotatedPage = False
|
||||||
if not options.noprocessing and "BlackBackground" in options.imgMetadata[imgfilepath]:
|
if not options.noprocessing and "BlackBackground" in options.imgMetadata[key]:
|
||||||
additionalStyle = 'background-color:#000000;'
|
additionalStyle = 'background-color:#000000;'
|
||||||
else:
|
else:
|
||||||
additionalStyle = ''
|
additionalStyle = ''
|
||||||
@@ -216,7 +216,7 @@ def buildNCX(dstdir, title, chapters, chapternames):
|
|||||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||||
navID = folder.replace('/', '_').replace('\\', '_')
|
navID = folder.replace('/', '_').replace('\\', '_')
|
||||||
if options.chapters:
|
if options.comicinfo_chapters:
|
||||||
title = chapternames[chapter[1]]
|
title = chapternames[chapter[1]]
|
||||||
navID = filename[0].replace('/', '_').replace('\\', '_')
|
navID = filename[0].replace('/', '_').replace('\\', '_')
|
||||||
elif os.path.basename(folder) != "Text":
|
elif os.path.basename(folder) != "Text":
|
||||||
@@ -244,7 +244,7 @@ def buildNAV(dstdir, title, chapters, chapternames):
|
|||||||
for chapter in chapters:
|
for chapter in chapters:
|
||||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||||
if options.chapters:
|
if options.comicinfo_chapters:
|
||||||
title = chapternames[chapter[1]]
|
title = chapternames[chapter[1]]
|
||||||
elif os.path.basename(folder) != "Text":
|
elif os.path.basename(folder) != "Text":
|
||||||
title = chapternames[os.path.basename(folder)]
|
title = chapternames[os.path.basename(folder)]
|
||||||
@@ -256,7 +256,7 @@ def buildNAV(dstdir, title, chapters, chapternames):
|
|||||||
for chapter in chapters:
|
for chapter in chapters:
|
||||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||||
if options.chapters:
|
if options.comicinfo_chapters:
|
||||||
title = chapternames[chapter[1]]
|
title = chapternames[chapter[1]]
|
||||||
elif os.path.basename(folder) != "Text":
|
elif os.path.basename(folder) != "Text":
|
||||||
title = chapternames[os.path.basename(folder)]
|
title = chapternames[os.path.basename(folder)]
|
||||||
@@ -358,19 +358,19 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
|||||||
pageside = "right"
|
pageside = "right"
|
||||||
for entry in reflist:
|
for entry in reflist:
|
||||||
if options.righttoleft:
|
if options.righttoleft:
|
||||||
if entry.endswith("-a"):
|
if entry.endswith("-kcc-a"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("center"))
|
pageSpreadProperty("center"))
|
||||||
)
|
)
|
||||||
pageside = "right"
|
pageside = "right"
|
||||||
elif entry.endswith("-b"):
|
elif entry.endswith("-kcc-b"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("right"))
|
pageSpreadProperty("right"))
|
||||||
)
|
)
|
||||||
pageside = "right"
|
pageside = "right"
|
||||||
elif entry.endswith("-c"):
|
elif entry.endswith("-kcc-c"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("left"))
|
pageSpreadProperty("left"))
|
||||||
@@ -386,19 +386,19 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
|||||||
else:
|
else:
|
||||||
pageside = "right"
|
pageside = "right"
|
||||||
else:
|
else:
|
||||||
if entry.endswith("-a"):
|
if entry.endswith("-kcc-a"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("center"))
|
pageSpreadProperty("center"))
|
||||||
)
|
)
|
||||||
pageside = "left"
|
pageside = "left"
|
||||||
elif entry.endswith("-b"):
|
elif entry.endswith("-kcc-b"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("left"))
|
pageSpreadProperty("left"))
|
||||||
)
|
)
|
||||||
pageside = "left"
|
pageside = "left"
|
||||||
elif entry.endswith("-c"):
|
elif entry.endswith("-kcc-c"):
|
||||||
f.write(
|
f.write(
|
||||||
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
|
||||||
pageSpreadProperty("right"))
|
pageSpreadProperty("right"))
|
||||||
@@ -425,7 +425,6 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
|||||||
"</container>"])
|
"</container>"])
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
def buildEPUB(path, chapternames, tomenumber, ischunked):
|
def buildEPUB(path, chapternames, tomenumber, ischunked):
|
||||||
filelist = []
|
filelist = []
|
||||||
chapterlist = []
|
chapterlist = []
|
||||||
@@ -506,6 +505,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked):
|
|||||||
"display: none;\n",
|
"display: none;\n",
|
||||||
"}\n"])
|
"}\n"])
|
||||||
f.close()
|
f.close()
|
||||||
|
build_html_start = perf_counter()
|
||||||
for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||||
chapter = False
|
chapter = False
|
||||||
dirnames, filenames = walkSort(dirnames, filenames)
|
dirnames, filenames = walkSort(dirnames, filenames)
|
||||||
@@ -515,12 +515,17 @@ def buildEPUB(path, chapternames, tomenumber, ischunked):
|
|||||||
'cover' + getImageFileName(afile)[1])
|
'cover' + getImageFileName(afile)[1])
|
||||||
options.covers.append((image.Cover(os.path.join(dirpath, afile), cover, options,
|
options.covers.append((image.Cover(os.path.join(dirpath, afile), cover, options,
|
||||||
tomenumber), options.uuid))
|
tomenumber), options.uuid))
|
||||||
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
|
|
||||||
if not chapter:
|
if not chapter:
|
||||||
chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1]))
|
chapterlist.append((dirpath.replace('Images', 'Text'), afile))
|
||||||
chapter = True
|
chapter = True
|
||||||
|
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
|
||||||
|
build_html_end = perf_counter()
|
||||||
|
print(f"buildHTML: {build_html_end - build_html_start} seconds")
|
||||||
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
|
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
|
||||||
if not chapternames and options.chapters and not ischunked:
|
if ischunked:
|
||||||
|
options.comicinfo_chapters = []
|
||||||
|
|
||||||
|
if not chapternames and options.comicinfo_chapters:
|
||||||
chapterlist = []
|
chapterlist = []
|
||||||
|
|
||||||
global_diff = 0
|
global_diff = 0
|
||||||
@@ -533,7 +538,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked):
|
|||||||
elif options.splitter == 2:
|
elif options.splitter == 2:
|
||||||
diff_delta = 2
|
diff_delta = 2
|
||||||
|
|
||||||
for aChapter in options.chapters:
|
for aChapter in options.comicinfo_chapters:
|
||||||
pageid = aChapter[0]
|
pageid = aChapter[0]
|
||||||
cur_diff = global_diff
|
cur_diff = global_diff
|
||||||
global_diff = 0
|
global_diff = 0
|
||||||
@@ -556,7 +561,6 @@ def imgDirectoryProcessing(path):
|
|||||||
workerPool = Pool(maxtasksperchild=100)
|
workerPool = Pool(maxtasksperchild=100)
|
||||||
workerOutput = []
|
workerOutput = []
|
||||||
options.imgMetadata = {}
|
options.imgMetadata = {}
|
||||||
options.imgOld = []
|
|
||||||
work = []
|
work = []
|
||||||
pagenumber = 0
|
pagenumber = 0
|
||||||
for dirpath, _, filenames in os.walk(path):
|
for dirpath, _, filenames in os.walk(path):
|
||||||
@@ -566,19 +570,19 @@ def imgDirectoryProcessing(path):
|
|||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit(str(pagenumber))
|
GUI.progressBarTick.emit(str(pagenumber))
|
||||||
if len(work) > 0:
|
if len(work) > 0:
|
||||||
|
img_processing_start = perf_counter()
|
||||||
for i in work:
|
for i in work:
|
||||||
workerPool.apply_async(func=imgFileProcessing, args=(i,), callback=imgFileProcessingTick)
|
workerPool.apply_async(func=imgFileProcessing, args=(i,), callback=imgFileProcessingTick)
|
||||||
workerPool.close()
|
workerPool.close()
|
||||||
workerPool.join()
|
workerPool.join()
|
||||||
|
img_processing_end = perf_counter()
|
||||||
|
print(f"imgFileProcessing: {img_processing_end - img_processing_start} seconds")
|
||||||
if GUI and not GUI.conversionAlive:
|
if GUI and not GUI.conversionAlive:
|
||||||
rmtree(os.path.join(path, '..', '..'), True)
|
rmtree(os.path.join(path, '..', '..'), True)
|
||||||
raise UserWarning("Conversion interrupted.")
|
raise UserWarning("Conversion interrupted.")
|
||||||
if len(workerOutput) > 0:
|
if len(workerOutput) > 0:
|
||||||
rmtree(os.path.join(path, '..', '..'), True)
|
rmtree(os.path.join(path, '..', '..'), True)
|
||||||
raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0][0], workerOutput[0][1])
|
raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0][0], workerOutput[0][1])
|
||||||
for file in options.imgOld:
|
|
||||||
if os.path.isfile(file):
|
|
||||||
os.remove(file)
|
|
||||||
else:
|
else:
|
||||||
rmtree(os.path.join(path, '..', '..'), True)
|
rmtree(os.path.join(path, '..', '..'), True)
|
||||||
raise UserWarning("Source directory is empty.")
|
raise UserWarning("Source directory is empty.")
|
||||||
@@ -592,7 +596,6 @@ def imgFileProcessingTick(output):
|
|||||||
for page in output:
|
for page in output:
|
||||||
if page is not None:
|
if page is not None:
|
||||||
options.imgMetadata[page[0]] = page[1]
|
options.imgMetadata[page[0]] = page[1]
|
||||||
options.imgOld.append(page[2])
|
|
||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit('tick')
|
GUI.progressBarTick.emit('tick')
|
||||||
if not GUI.conversionAlive:
|
if not GUI.conversionAlive:
|
||||||
@@ -657,16 +660,14 @@ def getWorkFolder(afile):
|
|||||||
path = cbx.extract(workdir)
|
path = cbx.extract(workdir)
|
||||||
sanitizePermissions(path)
|
sanitizePermissions(path)
|
||||||
tdir = os.listdir(workdir)
|
tdir = os.listdir(workdir)
|
||||||
is_nested_single_dir = False
|
|
||||||
if len(tdir) == 2 and 'ComicInfo.xml' in tdir:
|
if len(tdir) == 2 and 'ComicInfo.xml' in tdir:
|
||||||
tdir.remove('ComicInfo.xml')
|
tdir.remove('ComicInfo.xml')
|
||||||
is_nested_single_dir = os.path.isdir(os.path.join(workdir, tdir[0]))
|
if os.path.isdir(os.path.join(workdir, tdir[0])):
|
||||||
if is_nested_single_dir:
|
|
||||||
os.replace(
|
os.replace(
|
||||||
os.path.join(workdir, 'ComicInfo.xml'),
|
os.path.join(workdir, 'ComicInfo.xml'),
|
||||||
os.path.join(workdir, tdir[0], 'ComicInfo.xml')
|
os.path.join(workdir, tdir[0], 'ComicInfo.xml')
|
||||||
)
|
)
|
||||||
if len(tdir) == 1 and is_nested_single_dir:
|
if len(tdir) == 1 and os.path.isdir(os.path.join(workdir, tdir[0])):
|
||||||
path = 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)
|
||||||
@@ -674,8 +675,7 @@ def getWorkFolder(afile):
|
|||||||
else:
|
else:
|
||||||
raise UserWarning("Failed to open source file/directory.")
|
raise UserWarning("Failed to open source file/directory.")
|
||||||
newpath = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
newpath = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
||||||
copytree(path, os.path.join(newpath, 'OEBPS', 'Images'))
|
os.renames(path, os.path.join(newpath, 'OEBPS', 'Images'))
|
||||||
rmtree(workdir, True)
|
|
||||||
return newpath
|
return newpath
|
||||||
|
|
||||||
|
|
||||||
@@ -716,7 +716,7 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
|||||||
|
|
||||||
def getComicInfo(path, originalpath):
|
def getComicInfo(path, originalpath):
|
||||||
xmlPath = os.path.join(path, 'ComicInfo.xml')
|
xmlPath = os.path.join(path, 'ComicInfo.xml')
|
||||||
options.chapters = []
|
options.comicinfo_chapters = []
|
||||||
options.summary = ''
|
options.summary = ''
|
||||||
titleSuffix = ''
|
titleSuffix = ''
|
||||||
if options.title == 'defaulttitle':
|
if options.title == 'defaulttitle':
|
||||||
@@ -739,9 +739,7 @@ def getComicInfo(path, originalpath):
|
|||||||
except Exception:
|
except Exception:
|
||||||
os.remove(xmlPath)
|
os.remove(xmlPath)
|
||||||
return
|
return
|
||||||
if xml.data['Title']:
|
if defaultTitle:
|
||||||
options.title = hescape(xml.data['Title'])
|
|
||||||
elif defaultTitle:
|
|
||||||
if xml.data['Series']:
|
if xml.data['Series']:
|
||||||
options.title = hescape(xml.data['Series'])
|
options.title = hescape(xml.data['Series'])
|
||||||
if xml.data['Volume']:
|
if xml.data['Volume']:
|
||||||
@@ -760,7 +758,7 @@ def getComicInfo(path, originalpath):
|
|||||||
else:
|
else:
|
||||||
options.authors = ['KCC']
|
options.authors = ['KCC']
|
||||||
if xml.data['Bookmarks']:
|
if xml.data['Bookmarks']:
|
||||||
options.chapters = xml.data['Bookmarks']
|
options.comicinfo_chapters = xml.data['Bookmarks']
|
||||||
if xml.data['Summary']:
|
if xml.data['Summary']:
|
||||||
options.summary = hescape(xml.data['Summary'])
|
options.summary = hescape(xml.data['Summary'])
|
||||||
os.remove(xmlPath)
|
os.remove(xmlPath)
|
||||||
@@ -793,22 +791,22 @@ def getPanelViewSize(deviceres, size):
|
|||||||
|
|
||||||
def sanitizeTree(filetree):
|
def sanitizeTree(filetree):
|
||||||
chapterNames = {}
|
chapterNames = {}
|
||||||
for root, dirs, files in os.walk(filetree, False):
|
page = 1
|
||||||
for i, name in enumerate(os_sorted(files)):
|
for root, dirs, files in os.walk(filetree):
|
||||||
|
dirs.sort(key=OS_SORT_KEY)
|
||||||
|
files.sort(key=OS_SORT_KEY)
|
||||||
|
for name in files:
|
||||||
splitname = os.path.splitext(name)
|
splitname = os.path.splitext(name)
|
||||||
|
|
||||||
# file needs kcc at front AND back to avoid renaming issues
|
# 9999 page limit
|
||||||
slugified = f'kcc-{i:04}'
|
slugified = f'kcc-{page:04}'
|
||||||
for suffix in '-KCC', '-KCC-A', '-KCC-B', '-KCC-C':
|
page += 1
|
||||||
if splitname[0].endswith(suffix):
|
|
||||||
slugified += suffix.lower()
|
|
||||||
break
|
|
||||||
|
|
||||||
newKey = os.path.join(root, slugified + splitname[1])
|
newKey = os.path.join(root, slugified + splitname[1])
|
||||||
key = os.path.join(root, name)
|
key = os.path.join(root, name)
|
||||||
if key != newKey:
|
if key != newKey:
|
||||||
os.replace(key, newKey)
|
os.replace(key, newKey)
|
||||||
for name in dirs:
|
for i, name in enumerate(dirs):
|
||||||
tmpName = name
|
tmpName = name
|
||||||
slugified = slugify(name)
|
slugified = slugify(name)
|
||||||
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
|
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
|
||||||
@@ -818,6 +816,7 @@ def sanitizeTree(filetree):
|
|||||||
key = os.path.join(root, name)
|
key = os.path.join(root, name)
|
||||||
if key != newKey:
|
if key != newKey:
|
||||||
os.replace(key, newKey)
|
os.replace(key, newKey)
|
||||||
|
dirs[i] = newKey
|
||||||
return chapterNames
|
return chapterNames
|
||||||
|
|
||||||
|
|
||||||
@@ -890,8 +889,7 @@ def chunk_process(path, mode, parent):
|
|||||||
firstTome = False
|
firstTome = False
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def detectSuboptimalProcessing(tmppath, orgpath):
|
||||||
def detectCorruption(tmppath, orgpath):
|
|
||||||
imageNumber = 0
|
imageNumber = 0
|
||||||
imageSmaller = 0
|
imageSmaller = 0
|
||||||
alreadyProcessed = False
|
alreadyProcessed = False
|
||||||
@@ -907,9 +905,6 @@ def detectCorruption(tmppath, orgpath):
|
|||||||
raise RuntimeError('Image file %s is corrupted.' % pathOrg)
|
raise RuntimeError('Image file %s is corrupted.' % pathOrg)
|
||||||
try:
|
try:
|
||||||
img = Image.open(path)
|
img = Image.open(path)
|
||||||
img.verify()
|
|
||||||
img = Image.open(path)
|
|
||||||
img.load()
|
|
||||||
imageNumber += 1
|
imageNumber += 1
|
||||||
if options.profileData[1][0] > img.size[0] and options.profileData[1][1] > img.size[1]:
|
if options.profileData[1][0] > img.size[0] and options.profileData[1][1] > img.size[1]:
|
||||||
imageSmaller += 1
|
imageSmaller += 1
|
||||||
@@ -953,17 +948,27 @@ def slugify(value):
|
|||||||
|
|
||||||
|
|
||||||
def makeZIP(zipfilename, basedir, isepub=False):
|
def makeZIP(zipfilename, basedir, isepub=False):
|
||||||
|
start = perf_counter()
|
||||||
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
||||||
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
if '7z' in available_archive_tools():
|
||||||
if isepub:
|
if isepub:
|
||||||
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
|
mimetypeFile = open(os.path.join(basedir, 'mimetype'), 'w')
|
||||||
for dirpath, _, filenames in os.walk(basedir):
|
mimetypeFile.write('application/epub+zip')
|
||||||
for name in filenames:
|
mimetypeFile.close()
|
||||||
path = os.path.normpath(os.path.join(dirpath, name))
|
subprocess_run(['7z', 'a', '-tzip', zipfilename, os.path.join(basedir, "*")], capture_output=True, check=True)
|
||||||
aPath = os.path.normpath(os.path.join(dirpath.replace(basedir, ''), name))
|
else:
|
||||||
if os.path.isfile(path):
|
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
||||||
zipOutput.write(path, aPath)
|
if isepub:
|
||||||
zipOutput.close()
|
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
|
||||||
|
for dirpath, _, filenames in os.walk(basedir):
|
||||||
|
for name in filenames:
|
||||||
|
path = os.path.normpath(os.path.join(dirpath, name))
|
||||||
|
aPath = os.path.normpath(os.path.join(dirpath.replace(basedir, ''), name))
|
||||||
|
if os.path.isfile(path):
|
||||||
|
zipOutput.write(path, aPath)
|
||||||
|
zipOutput.close()
|
||||||
|
end = perf_counter()
|
||||||
|
print(f"makeZIP time: {end - start} seconds")
|
||||||
return zipfilename
|
return zipfilename
|
||||||
|
|
||||||
|
|
||||||
@@ -1138,9 +1143,7 @@ def checkTools(source):
|
|||||||
source = source.upper()
|
source = source.upper()
|
||||||
if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \
|
if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \
|
||||||
source.endswith('.ZIP') or source.endswith('.CBZ'):
|
source.endswith('.ZIP') or source.endswith('.CBZ'):
|
||||||
try:
|
if '7z' not in available_archive_tools():
|
||||||
subprocess_run(['7z'], stdout=PIPE, stderr=STDOUT)
|
|
||||||
except FileNotFoundError:
|
|
||||||
print('ERROR: 7z is missing!')
|
print('ERROR: 7z is missing!')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if options.format == 'MOBI':
|
if options.format == 'MOBI':
|
||||||
@@ -1170,6 +1173,7 @@ def checkPre(source):
|
|||||||
|
|
||||||
|
|
||||||
def makeBook(source, qtgui=None):
|
def makeBook(source, qtgui=None):
|
||||||
|
start = perf_counter()
|
||||||
global GUI
|
global GUI
|
||||||
GUI = qtgui
|
GUI = qtgui
|
||||||
if GUI:
|
if GUI:
|
||||||
@@ -1181,7 +1185,8 @@ def makeBook(source, qtgui=None):
|
|||||||
path = getWorkFolder(source)
|
path = getWorkFolder(source)
|
||||||
print("Checking images...")
|
print("Checking images...")
|
||||||
getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
|
getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
|
||||||
detectCorruption(os.path.join(path, "OEBPS", "Images"), source)
|
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
|
||||||
|
chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||||
if options.webtoon:
|
if options.webtoon:
|
||||||
y = image.ProfileData.Profiles[options.profile][1][1]
|
y = image.ProfileData.Profiles[options.profile][1][1]
|
||||||
comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtgui)
|
comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtgui)
|
||||||
@@ -1194,7 +1199,6 @@ def makeBook(source, qtgui=None):
|
|||||||
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"))
|
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"))
|
||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit('1')
|
GUI.progressBarTick.emit('1')
|
||||||
chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
|
||||||
if options.batchsplit > 0:
|
if options.batchsplit > 0:
|
||||||
tomes = chunk_directory(path)
|
tomes = chunk_directory(path)
|
||||||
else:
|
else:
|
||||||
@@ -1272,6 +1276,8 @@ def makeBook(source, qtgui=None):
|
|||||||
elif os.path.isdir(source):
|
elif os.path.isdir(source):
|
||||||
rmtree(source)
|
rmtree(source)
|
||||||
|
|
||||||
|
end = perf_counter()
|
||||||
|
print(f"makeBook: {end - start} seconds")
|
||||||
return filepath
|
return filepath
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ class ComicArchive:
|
|||||||
['unar', self.filepath, '-f', '-o', targetdir]
|
['unar', self.filepath, '-f', '-o', targetdir]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
extraction_commands.reverse()
|
||||||
|
|
||||||
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
||||||
extraction_commands.append(
|
extraction_commands.append(
|
||||||
['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.filepath, targetdir]
|
['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.filepath, targetdir]
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import mozjpeg_lossless_optimization
|
import mozjpeg_lossless_optimization
|
||||||
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
||||||
from .shared import md5Checksum
|
|
||||||
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
|
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
|
||||||
from .inter_panel_crop_alg import crop_empty_inter_panel
|
from .inter_panel_crop_alg import crop_empty_inter_panel
|
||||||
|
|
||||||
@@ -141,7 +141,13 @@ class ComicPageParser:
|
|||||||
self.source = source
|
self.source = source
|
||||||
self.size = self.opt.profileData[1]
|
self.size = self.opt.profileData[1]
|
||||||
self.payload = []
|
self.payload = []
|
||||||
self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB')
|
|
||||||
|
# Detect corruption in source image, let caller catch any exceptions triggered.
|
||||||
|
srcImgPath = os.path.join(source[0], source[1])
|
||||||
|
self.image = Image.open(srcImgPath)
|
||||||
|
self.image.verify()
|
||||||
|
self.image = Image.open(srcImgPath).convert('RGB')
|
||||||
|
|
||||||
self.color = self.colorCheck()
|
self.color = self.colorCheck()
|
||||||
self.fill = self.fillCheck()
|
self.fill = self.fillCheck()
|
||||||
# backwards compatibility for Pillow >9.1.0
|
# backwards compatibility for Pillow >9.1.0
|
||||||
@@ -279,14 +285,14 @@ class ComicPage:
|
|||||||
self.rotated = False
|
self.rotated = False
|
||||||
self.orgPath = os.path.join(path[0], path[1])
|
self.orgPath = os.path.join(path[0], path[1])
|
||||||
if 'N' in mode:
|
if 'N' in mode:
|
||||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC'
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-kcc'
|
||||||
elif 'R' in mode:
|
elif 'R' in mode:
|
||||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-A'
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-kcc-a'
|
||||||
self.rotated = True
|
self.rotated = True
|
||||||
elif 'S1' in mode:
|
elif 'S1' in mode:
|
||||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-B'
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-kcc-b'
|
||||||
elif 'S2' in mode:
|
elif 'S2' in mode:
|
||||||
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC-C'
|
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-kcc-c'
|
||||||
# backwards compatibility for Pillow >9.1.0
|
# backwards compatibility for Pillow >9.1.0
|
||||||
if not hasattr(Image, 'Resampling'):
|
if not hasattr(Image, 'Resampling'):
|
||||||
Image.Resampling = Image
|
Image.Resampling = Image
|
||||||
@@ -315,7 +321,9 @@ class ComicPage:
|
|||||||
output_jpeg_file.write(output_jpeg_bytes)
|
output_jpeg_file.write(output_jpeg_bytes)
|
||||||
else:
|
else:
|
||||||
self.image.save(self.targetPath, 'JPEG', optimize=1, quality=85)
|
self.image.save(self.targetPath, 'JPEG', optimize=1, quality=85)
|
||||||
return [md5Checksum(self.targetPath), flags, self.orgPath]
|
if os.path.isfile(self.orgPath):
|
||||||
|
os.remove(self.orgPath)
|
||||||
|
return [Path(self.targetPath).name, flags]
|
||||||
except IOError as err:
|
except IOError as err:
|
||||||
raise RuntimeError('Cannot save image. ' + str(err))
|
raise RuntimeError('Cannot save image. ' + str(err))
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from functools import lru_cache
|
||||||
import os
|
import os
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
@@ -74,16 +75,6 @@ def walkLevel(some_dir, level=1):
|
|||||||
del dirs[:]
|
del dirs[:]
|
||||||
|
|
||||||
|
|
||||||
def md5Checksum(fpath):
|
|
||||||
with open(fpath, 'rb') as fh:
|
|
||||||
m = md5()
|
|
||||||
while True:
|
|
||||||
data = fh.read(8192)
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
m.update(data)
|
|
||||||
return m.hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
def sanitizeTrace(traceback):
|
def sanitizeTrace(traceback):
|
||||||
return ''.join(format_tb(traceback))\
|
return ''.join(format_tb(traceback))\
|
||||||
@@ -137,6 +128,19 @@ def dependencyCheck(level):
|
|||||||
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def available_archive_tools():
|
||||||
|
available = []
|
||||||
|
|
||||||
|
for tool in ['tar', '7z', 'unar', 'unrar']:
|
||||||
|
try:
|
||||||
|
subprocess_run([tool], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
available.append(tool)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return available
|
||||||
|
|
||||||
def subprocess_run(command, **kwargs):
|
def subprocess_run(command, **kwargs):
|
||||||
if (os.name == 'nt'):
|
if (os.name == 'nt'):
|
||||||
kwargs.setdefault('creationflags', subprocess.CREATE_NO_WINDOW)
|
kwargs.setdefault('creationflags', subprocess.CREATE_NO_WINDOW)
|
||||||
|
|||||||
Reference in New Issue
Block a user