mirror of
https://github.com/ciromattia/kcc
synced 2026-04-15 05:28:49 +00:00
Compare commits
15 Commits
v7.2.3
...
revert-845
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46a6103e19 | ||
|
|
24ab72fcbc | ||
|
|
4af6a75874 | ||
|
|
28b6188a3f | ||
|
|
f000af1207 | ||
|
|
299f916580 | ||
|
|
c39e403595 | ||
|
|
e787dd2897 | ||
|
|
01625904d1 | ||
|
|
5f8526da44 | ||
|
|
1159e737a0 | ||
|
|
5bbdb715e9 | ||
|
|
1a3cd6c916 | ||
|
|
e1e6d587f4 | ||
|
|
ca5c0bdd61 |
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
|
||||
|
||||
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) 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
|
||||
any purpose with or without fee is hereby granted, provided that the
|
||||
|
||||
10
README.md
10
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**_.
|
||||
It can also optionally optimize images by applying a number of transformations.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
||||
@@ -68,9 +70,11 @@ If you have issues detecting it, get stuck on the MOBI conversion step, or use L
|
||||
|
||||
### 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
|
||||
|
||||
@@ -317,5 +321,5 @@ The app relies and includes the following scripts:
|
||||
Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||
|
||||
## 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.
|
||||
|
||||
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*
|
||||
@@ -37,7 +37,7 @@ from packaging.version import Version
|
||||
from raven import Client
|
||||
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 comic2ebook
|
||||
from . import metadata
|
||||
@@ -317,13 +317,8 @@ class WorkerThread(QThread):
|
||||
GUI.progress.content = ''
|
||||
self.errors = True
|
||||
_, _, traceback = sys.exc_info()
|
||||
if len(err.args) == 1:
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (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]})
|
||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
||||
if ' is corrupted.' not in str(err):
|
||||
GUI.sentry.captureException()
|
||||
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 '
|
||||
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
||||
'info')
|
||||
try:
|
||||
subprocess_run(['tar'], stdout=PIPE, stderr=STDOUT)
|
||||
self.tar = True
|
||||
except FileNotFoundError:
|
||||
self.tar = False
|
||||
try:
|
||||
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.tar = 'tar' in available_archive_tools()
|
||||
self.sevenzip = '7z' in available_archive_tools()
|
||||
if not any([self.tar, self.sevenzip]):
|
||||
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)
|
||||
|
||||
APP.messageFromOtherInstance.connect(self.handleMessage)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = '7.2.3'
|
||||
__version__ = '7.3.0'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -23,7 +23,7 @@ import pathlib
|
||||
import re
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from time import strftime, gmtime
|
||||
from time import perf_counter, strftime, gmtime
|
||||
from copy import copy
|
||||
from glob import glob, escape
|
||||
from re import sub
|
||||
@@ -33,14 +33,14 @@ from tempfile import mkdtemp, gettempdir, TemporaryFile
|
||||
from shutil import move, copytree, rmtree, copyfile
|
||||
from multiprocessing import Pool
|
||||
from uuid import uuid4
|
||||
from natsort import os_sorted
|
||||
from natsort import os_sort_keygen
|
||||
from slugify import slugify as slugify_ext
|
||||
from PIL import Image, ImageFile
|
||||
from subprocess import STDOUT, PIPE
|
||||
from psutil import virtual_memory, disk_usage
|
||||
from html import escape as hescape
|
||||
|
||||
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
||||
from .shared import available_archive_tools, md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
||||
from . import comic2panel
|
||||
from . import image
|
||||
from . import comicarchive
|
||||
@@ -51,7 +51,7 @@ from . import kindle
|
||||
from . import __version__
|
||||
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
|
||||
OS_SORT_KEY = os_sort_keygen()
|
||||
|
||||
def main(argv=None):
|
||||
global options
|
||||
@@ -790,12 +790,16 @@ def getPanelViewSize(deviceres, size):
|
||||
|
||||
def sanitizeTree(filetree):
|
||||
chapterNames = {}
|
||||
for root, dirs, files in os.walk(filetree, False):
|
||||
for i, name in enumerate(os_sorted(files)):
|
||||
page = 1
|
||||
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)
|
||||
|
||||
# file needs kcc at front AND back to avoid renaming issues
|
||||
slugified = f'kcc-{i:04}'
|
||||
slugified = f'kcc-{page:04}'
|
||||
page += 1
|
||||
for suffix in '-KCC', '-KCC-A', '-KCC-B', '-KCC-C':
|
||||
if splitname[0].endswith(suffix):
|
||||
slugified += suffix.lower()
|
||||
@@ -805,7 +809,7 @@ def sanitizeTree(filetree):
|
||||
key = os.path.join(root, name)
|
||||
if key != newKey:
|
||||
os.replace(key, newKey)
|
||||
for name in dirs:
|
||||
for i, name in enumerate(dirs):
|
||||
tmpName = name
|
||||
slugified = slugify(name)
|
||||
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
|
||||
@@ -815,6 +819,7 @@ def sanitizeTree(filetree):
|
||||
key = os.path.join(root, name)
|
||||
if key != newKey:
|
||||
os.replace(key, newKey)
|
||||
dirs[i] = newKey
|
||||
return chapterNames
|
||||
|
||||
|
||||
@@ -946,17 +951,27 @@ def slugify(value):
|
||||
|
||||
|
||||
def makeZIP(zipfilename, basedir, isepub=False):
|
||||
start = perf_counter()
|
||||
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
||||
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
||||
if isepub:
|
||||
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()
|
||||
if '7z' in available_archive_tools():
|
||||
if isepub:
|
||||
mimetypeFile = open(os.path.join(basedir, 'mimetype'), 'w')
|
||||
mimetypeFile.write('application/epub+zip')
|
||||
mimetypeFile.close()
|
||||
subprocess_run(['7z', 'a', '-tzip', zipfilename, os.path.join(basedir, "*")], capture_output=True, check=True)
|
||||
else:
|
||||
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
||||
if isepub:
|
||||
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
|
||||
|
||||
|
||||
@@ -1131,9 +1146,7 @@ def checkTools(source):
|
||||
source = source.upper()
|
||||
if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \
|
||||
source.endswith('.ZIP') or source.endswith('.CBZ'):
|
||||
try:
|
||||
subprocess_run(['7z'], stdout=PIPE, stderr=STDOUT)
|
||||
except FileNotFoundError:
|
||||
if '7z' not in available_archive_tools():
|
||||
print('ERROR: 7z is missing!')
|
||||
sys.exit(1)
|
||||
if options.format == 'MOBI':
|
||||
@@ -1163,6 +1176,7 @@ def checkPre(source):
|
||||
|
||||
|
||||
def makeBook(source, qtgui=None):
|
||||
start = perf_counter()
|
||||
global GUI
|
||||
GUI = qtgui
|
||||
if GUI:
|
||||
@@ -1265,6 +1279,8 @@ def makeBook(source, qtgui=None):
|
||||
elif os.path.isdir(source):
|
||||
rmtree(source)
|
||||
|
||||
end = perf_counter()
|
||||
print(f"makeBook: {end - start} seconds")
|
||||
return filepath
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
|
||||
from functools import lru_cache
|
||||
import os
|
||||
from hashlib import md5
|
||||
from html.parser import HTMLParser
|
||||
@@ -137,6 +138,19 @@ def dependencyCheck(level):
|
||||
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||
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):
|
||||
if (os.name == 'nt'):
|
||||
kwargs.setdefault('creationflags', subprocess.CREATE_NO_WINDOW)
|
||||
|
||||
Reference in New Issue
Block a user