mirror of
https://github.com/ciromattia/kcc
synced 2026-04-15 13:38:46 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c6f591e45 | ||
|
|
78c014bf22 | ||
|
|
421e6bcb64 | ||
|
|
5168cd73c4 | ||
|
|
f7f19b99da | ||
|
|
1131bab41f | ||
|
|
8d204668a7 | ||
|
|
99d94ceaa7 | ||
|
|
8ff401cc3a | ||
|
|
fb7d92d737 | ||
|
|
1ca8b2c11b | ||
|
|
40e0b4853b | ||
|
|
005313f978 | ||
|
|
add2ef9faa | ||
|
|
0c98acd606 | ||
|
|
fe902ec213 | ||
|
|
4e9714e6f8 | ||
|
|
1337ad7fe3 | ||
|
|
511c7c1580 | ||
|
|
16dd034af4 | ||
|
|
d22794555f | ||
|
|
ab089adbca | ||
|
|
2c770f4562 | ||
|
|
4c73006bd8 | ||
|
|
1093dbf65a | ||
|
|
df9990c692 | ||
|
|
114f4c9e57 | ||
|
|
c2c477475d | ||
|
|
a0f8d0b5cf |
@@ -1,5 +1,5 @@
|
||||
# Select final stage based on TARGETARCH ARG
|
||||
FROM ghcr.io/ciromattia/kcc:docker-base-20230809
|
||||
FROM ghcr.io/ciromattia/kcc:docker-base-20240928
|
||||
LABEL com.kcc.name="Kindle Comic Converter"
|
||||
LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi"
|
||||
LABEL org.opencontainers.image.description='Kindle Comic Converter'
|
||||
@@ -16,4 +16,4 @@ COPY . /opt/kcc
|
||||
RUN cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
|
||||
|
||||
ENTRYPOINT ["/opt/kcc/kcc-c2e.py"]
|
||||
CMD ["-h"]
|
||||
CMD ["-h"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=linux/amd64 python:3.11-slim-bullseye as compile-amd64
|
||||
FROM --platform=linux/amd64 python:3.12-slim-bullseye as compile-amd64
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
@@ -16,7 +16,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||
|
||||
######################################################################################
|
||||
|
||||
FROM --platform=linux/arm64 python:3.11-slim-bullseye as compile-arm64
|
||||
FROM --platform=linux/arm64 python:3.12-slim-bullseye as compile-arm64
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
@@ -28,6 +28,9 @@ ENV LC_ALL=C.UTF-8 \
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
COPY requirements.txt /opt/kcc/
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
RUN set -x && \
|
||||
TEMP_PACKAGES=() && \
|
||||
KEPT_PACKAGES=() && \
|
||||
@@ -64,14 +67,13 @@ RUN set -x && \
|
||||
&& \
|
||||
# Install required python modules
|
||||
python -m pip install --upgrade pip && \
|
||||
# python -m pip install -r /opt/kcc/requirements.txt && \
|
||||
python -m venv /opt/venv && \
|
||||
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
|
||||
python -m pip install -r /opt/kcc/requirements.txt
|
||||
|
||||
|
||||
######################################################################################
|
||||
|
||||
FROM --platform=linux/arm/v7 python:3.11-slim-bullseye as compile-armv7
|
||||
FROM --platform=linux/arm/v7 python:3.12-slim-bullseye as compile-armv7
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
@@ -83,6 +85,9 @@ ENV LC_ALL=C.UTF-8 \
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
COPY requirements.txt /opt/kcc/
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
RUN set -x && \
|
||||
TEMP_PACKAGES=() && \
|
||||
KEPT_PACKAGES=() && \
|
||||
@@ -120,19 +125,18 @@ RUN set -x && \
|
||||
&& \
|
||||
# Install required python modules
|
||||
python -m pip install --upgrade pip && \
|
||||
# python -m pip install -r /opt/kcc/requirements.txt && \
|
||||
python -m venv /opt/venv && \
|
||||
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
|
||||
python -m pip install --upgrade pillow psutil requests python-slugify raven packaging mozjpeg-lossless-optimization natsort distro
|
||||
|
||||
|
||||
######################################################################################
|
||||
FROM --platform=linux/amd64 python:3.11-slim-bullseye as build-amd64
|
||||
FROM --platform=linux/amd64 python:3.12-slim-bullseye as build-amd64
|
||||
COPY --from=compile-amd64 /opt/venv /opt/venv
|
||||
|
||||
FROM --platform=linux/arm64 python:3.11-slim-bullseye as build-arm64
|
||||
FROM --platform=linux/arm64 python:3.12-slim-bullseye as build-arm64
|
||||
COPY --from=compile-arm64 /opt/venv /opt/venv
|
||||
|
||||
FROM --platform=linux/arm/v7 python:3.11-slim-bullseye as build-armv7
|
||||
FROM --platform=linux/arm/v7 python:3.12-slim-bullseye as build-armv7
|
||||
COPY --from=compile-armv7 /opt/venv /opt/venv
|
||||
######################################################################################
|
||||
|
||||
@@ -156,5 +160,5 @@ WORKDIR /app
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||
apt-get install -y p7zip-full unrar-free && \
|
||||
ln -s /app/kindlegen /bin/kindlegen && \
|
||||
echo docker-base-20230809 > /IMAGE_VERSION
|
||||
echo docker-base-20240928 > /IMAGE_VERSION
|
||||
|
||||
|
||||
20
README.md
20
README.md
@@ -22,13 +22,13 @@ If you have some **technical** problems using KCC please [file an issue here](ht
|
||||
If you can fix an open issue, fork & make a pull request.
|
||||
|
||||
If you find **KCC** valuable you can consider donating to the authors:
|
||||
- Ciro Mattia Gonano:
|
||||
- Ciro Mattia Gonano (founder, active 2013-2014):
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D8WNYNPBGDAS2)
|
||||
- [](http://flattr.com/thing/2260449/ciromattiakcc-on-GitHub)
|
||||
- Paweł Jastrzębski:
|
||||
- Paweł Jastrzębski (active 2013-2019):
|
||||
- [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
|
||||
- [](https://jastrzeb.ski/donate/)
|
||||
- Alex Xu
|
||||
- Alex Xu (active 2023-Present)
|
||||
- [](https://www.paypal.com/donate/?business=QFJVE7A6LCP6U&no_recurring=0&item_name=Kindle+Comic+Converter¤cy_code=USD)
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
|
||||
|
||||
- [Kindle Scribe cover guide](https://github.com/ciromattia/kcc/issues/508) (also works for older Kindles)
|
||||
- [Windows 7 support](https://github.com/ciromattia/kcc/issues/678)
|
||||
- [Combine files/chapters](https://github.com/ciromattia/kcc/issues/612#issuecomment-2117985011)
|
||||
- [Flatpak mobi conversion stuck](https://github.com/ciromattia/kcc/wiki/Installation#linux)
|
||||
|
||||
## PREREQUISITES
|
||||
|
||||
@@ -62,17 +64,15 @@ You'll need to install various tools to access important but optional features.
|
||||
|
||||
### KindleGen
|
||||
|
||||
#### Windows / macOS KindleGen
|
||||
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.
|
||||
|
||||
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, or use another OS, 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
|
||||
|
||||
### 7-Zip
|
||||
|
||||
This is an optional requirement as of KCC 6.1.0. You only need to install it if 1) you are using advanced features, 2) are using Windows 10 (2017) or earlier, or 3) need to use an older KCC version.
|
||||
This is no longer required as of KCC 6.1.
|
||||
|
||||
If you need to install it, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation#7-zip
|
||||
If you still need it, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation#7-zip
|
||||
|
||||
## INPUT FORMATS
|
||||
**KCC** can understand and convert, at the moment, the following input types:
|
||||
@@ -107,7 +107,7 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi
|
||||
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
|
||||
'KV': ("Kindle Paperwhite 3/4/Voyage/Oasis", (1072, 1448), Palette16, 1.8),
|
||||
'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.8),
|
||||
'KO': ("Kindle Oasis 2/3", (1264, 1680), Palette16, 1.8),
|
||||
'KO': ("Kindle Oasis 2/3/Paperwhite 12/Colorsoft 12", (1264, 1680), Palette16, 1.8),
|
||||
'KS': ("Kindle Scribe", (1860, 2480), Palette16, 1.8),
|
||||
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
|
||||
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
|
||||
|
||||
@@ -8,7 +8,7 @@ a = Analysis(['kcc-c2e.py'],
|
||||
pathex=['.'],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hiddenimports=['_cffi_backend'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
|
||||
@@ -8,7 +8,7 @@ a = Analysis(['kcc-c2p.py'],
|
||||
pathex=['.'],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hiddenimports=['_cffi_backend'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
|
||||
1
kcc.py
1
kcc.py
@@ -50,6 +50,7 @@ elif sys.platform.startswith('win'):
|
||||
win_paths = [
|
||||
os.path.expandvars('%LOCALAPPDATA%\\Amazon\\KC2'),
|
||||
os.path.expandvars('%LOCALAPPDATA%\\Amazon\\Kindle Previewer 3\\lib\\fc\\bin\\'),
|
||||
os.path.expandvars('%UserProfile%\\Kindle Previewer 3\\lib\\fc\\bin\\'),
|
||||
'C:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
'D:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
'E:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
|
||||
@@ -31,11 +31,11 @@ from PySide6.QtCore import Qt
|
||||
from xml.sax.saxutils import escape
|
||||
from psutil import Process
|
||||
from copy import copy
|
||||
from distutils.version import StrictVersion
|
||||
from packaging.version import Version
|
||||
from raven import Client
|
||||
from tempfile import gettempdir
|
||||
|
||||
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run_silent
|
||||
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run
|
||||
from . import __version__
|
||||
from . import comic2ebook
|
||||
from . import metadata
|
||||
@@ -146,9 +146,9 @@ class VersionThread(QtCore.QThread):
|
||||
latest_version = json_parser["tag_name"]
|
||||
latest_version = re.sub(r'^v', "", latest_version)
|
||||
|
||||
if ("b" not in __version__ and StrictVersion(latest_version) > StrictVersion(__version__)) \
|
||||
if ("b" not in __version__ and Version(latest_version) > Version(__version__)) \
|
||||
or ("b" in __version__
|
||||
and StrictVersion(latest_version) >= StrictVersion(re.sub(r'b.*', '', __version__))):
|
||||
and Version(latest_version) >= Version(re.sub(r'b.*', '', __version__))):
|
||||
MW.addMessage.emit('<a href="' + html_url + '"><b>The new version is available!</b></a>', 'warning',
|
||||
False)
|
||||
except Exception:
|
||||
@@ -380,7 +380,7 @@ class WorkerThread(QtCore.QThread):
|
||||
except Exception:
|
||||
pass
|
||||
MW.addMessage.emit('Processing MOBI files... <b>Done!</b>', 'info', True)
|
||||
k = kindle.Kindle()
|
||||
k = kindle.Kindle(options.profile)
|
||||
if k.path and k.coverSupport:
|
||||
for item in outputPath:
|
||||
comic2ebook.options.covers[outputPath.index(item)][0].saveToKindle(
|
||||
@@ -846,12 +846,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
versionCheck = subprocess_run_silent(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, encoding='UTF-8')
|
||||
versionCheck = subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, encoding='UTF-8')
|
||||
self.kindleGen = True
|
||||
for line in versionCheck.stdout.splitlines():
|
||||
if 'Amazon kindlegen' in line:
|
||||
versionCheck = line.split('V')[1].split(' ')[0]
|
||||
if StrictVersion(versionCheck) < StrictVersion('2.9'):
|
||||
if Version(versionCheck) < Version('2.9'):
|
||||
self.addMessage('Your <a href="https://www.amazon.com/b?node=23496309011">KindleGen</a>'
|
||||
' is outdated! MOBI conversion might fail.', 'warning')
|
||||
break
|
||||
@@ -934,6 +934,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
"Kindle PW 11": {
|
||||
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW5',
|
||||
},
|
||||
"Kindle PW 12": {
|
||||
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KO',
|
||||
},
|
||||
"Kindle CS 12": {
|
||||
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': True, 'Label': 'KO',
|
||||
},
|
||||
"Kindle PW 7/10": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KV'},
|
||||
"Kindle PW 5/6": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
|
||||
@@ -988,9 +994,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'Label': 'OTHER'},
|
||||
}
|
||||
profilesGUI = [
|
||||
"Kindle CS 12",
|
||||
"Kindle PW 12",
|
||||
"Kindle Scribe",
|
||||
"Kindle 11",
|
||||
"Kindle PW 11",
|
||||
"Kindle 11",
|
||||
"Kindle Oasis 9/10",
|
||||
"Separator",
|
||||
"Kobo Clara 2E",
|
||||
@@ -1041,12 +1049,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
||||
'info')
|
||||
try:
|
||||
subprocess_run_silent(['tar'], stdout=PIPE, stderr=STDOUT)
|
||||
subprocess_run(['tar'], stdout=PIPE, stderr=STDOUT)
|
||||
self.tar = True
|
||||
except FileNotFoundError:
|
||||
self.tar = False
|
||||
try:
|
||||
subprocess_run_silent(['7z'], stdout=PIPE, stderr=STDOUT)
|
||||
subprocess_run(['7z'], stdout=PIPE, stderr=STDOUT)
|
||||
self.sevenzip = True
|
||||
except FileNotFoundError:
|
||||
self.sevenzip = False
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = '6.1.0'
|
||||
__version__ = '6.3.0'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -40,7 +40,7 @@ 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_silent
|
||||
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
||||
from . import comic2panel
|
||||
from . import image
|
||||
from . import comicarchive
|
||||
@@ -420,7 +420,7 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
||||
f.close()
|
||||
|
||||
|
||||
def buildEPUB(path, chapternames, tomenumber):
|
||||
def buildEPUB(path, chapternames, tomenumber, ischunked):
|
||||
filelist = []
|
||||
chapterlist = []
|
||||
cover = None
|
||||
@@ -517,7 +517,7 @@ def buildEPUB(path, chapternames, tomenumber):
|
||||
chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1]))
|
||||
chapter = True
|
||||
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
|
||||
if not chapternames and options.chapters:
|
||||
if not chapternames and options.chapters and not ischunked:
|
||||
chapterlist = []
|
||||
|
||||
global_diff = 0
|
||||
@@ -738,7 +738,7 @@ def getComicInfo(path, originalpath):
|
||||
options.authors.sort()
|
||||
else:
|
||||
options.authors = ['KCC']
|
||||
if xml.data['Bookmarks'] and options.batchsplit == 0:
|
||||
if xml.data['Bookmarks']:
|
||||
options.chapters = xml.data['Bookmarks']
|
||||
if xml.data['Summary']:
|
||||
options.summary = hescape(xml.data['Summary'])
|
||||
@@ -957,8 +957,7 @@ def makeParser():
|
||||
help="Full path to comic folder or file(s) to be processed.")
|
||||
|
||||
main_options.add_argument("-p", "--profile", action="store", dest="profile", default="KV",
|
||||
help="Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KPW5, KV, KO, "
|
||||
"K11, KS, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoCC, KoL, KoLC, KoF, KoS, KoE)"
|
||||
help=f"Device profile (Available options: {', '.join(image.ProfileData.Profiles.keys())})"
|
||||
" [Default=KV]")
|
||||
main_options.add_argument("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
|
||||
help="Manga style (right-to-left reading and splitting)")
|
||||
@@ -1046,16 +1045,15 @@ def checkOptions(options):
|
||||
options.kfx = False
|
||||
options.supportSyntheticSpread = False
|
||||
if options.format == 'Auto':
|
||||
if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KPW5', 'KV', 'KO', 'K11', 'KS']:
|
||||
options.format = 'MOBI'
|
||||
elif options.profile in ['OTHER', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O', 'KoAO',
|
||||
'KoN', 'KoC', 'KoCC', 'KoL', 'KoLC', 'KoF', 'KoS', 'KoE']:
|
||||
options.format = 'EPUB'
|
||||
elif options.profile in ['KDX']:
|
||||
if options.profile in ['KDX']:
|
||||
options.format = 'CBZ'
|
||||
if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KPW5', 'KV', 'KO', 'K11', 'KS']:
|
||||
elif options.profile in image.ProfileData.ProfilesKindle.keys():
|
||||
options.format = 'MOBI'
|
||||
else:
|
||||
options.format = 'EPUB'
|
||||
if options.profile in image.ProfileData.ProfilesKindle.keys():
|
||||
options.iskindle = True
|
||||
elif options.profile in ['OTHER', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O', 'KoAO', 'KoN', 'KoC', 'KoCC', 'KoL', 'KoLC', 'KoF', 'KoS', 'KoE']:
|
||||
else:
|
||||
options.isKobo = True
|
||||
# Other Kobo devices probably support synthetic spreads as well, but
|
||||
# they haven't been tested.
|
||||
@@ -1114,13 +1112,13 @@ def checkTools(source):
|
||||
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_silent(['7z'], stdout=PIPE, stderr=STDOUT)
|
||||
subprocess_run(['7z'], stdout=PIPE, stderr=STDOUT)
|
||||
except FileNotFoundError:
|
||||
print('ERROR: 7z is missing!')
|
||||
sys.exit(1)
|
||||
if options.format == 'MOBI':
|
||||
try:
|
||||
subprocess_run_silent(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT)
|
||||
subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT)
|
||||
except FileNotFoundError:
|
||||
print('ERROR: KindleGen is missing!')
|
||||
sys.exit(1)
|
||||
@@ -1202,10 +1200,11 @@ def makeBook(source, qtgui=None):
|
||||
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"))
|
||||
else:
|
||||
print("Creating EPUB file...")
|
||||
buildEPUB(tome, chapterNames, tomeNumber)
|
||||
if len(tomes) > 1:
|
||||
buildEPUB(tome, chapterNames, tomeNumber, True)
|
||||
filepath.append(getOutputFilename(source, options.output, '.epub', ' ' + str(tomeNumber)))
|
||||
else:
|
||||
buildEPUB(tome, chapterNames, tomeNumber, False)
|
||||
filepath.append(getOutputFilename(source, options.output, '.epub', ''))
|
||||
makeZIP(tome + '_comic', tome, True)
|
||||
copyfile(tome + '_comic.zip', filepath[-1])
|
||||
@@ -1228,7 +1227,7 @@ def makeBook(source, qtgui=None):
|
||||
print('Error: KindleGen failed to create MOBI!')
|
||||
print(errors)
|
||||
return filepath
|
||||
k = kindle.Kindle()
|
||||
k = kindle.Kindle(options.profile)
|
||||
if k.path and k.coverSupport:
|
||||
print("Kindle detected. Uploading covers...")
|
||||
for i in filepath:
|
||||
@@ -1250,12 +1249,13 @@ def makeBook(source, qtgui=None):
|
||||
|
||||
|
||||
def makeMOBIFix(item, uuid):
|
||||
is_pdoc = options.profile in image.ProfileData.ProfilesKindlePDOC.keys()
|
||||
if not options.keep_epub:
|
||||
os.remove(item)
|
||||
mobiPath = item.replace('.epub', '.mobi')
|
||||
move(mobiPath, mobiPath + '_toclean')
|
||||
try:
|
||||
dualmetafix.DualMobiMetaFix(mobiPath + '_toclean', mobiPath, bytes(uuid, 'UTF-8'))
|
||||
dualmetafix.DualMobiMetaFix(mobiPath + '_toclean', mobiPath, bytes(uuid, 'UTF-8'), is_pdoc)
|
||||
return [True]
|
||||
except Exception as err:
|
||||
return [False, format(err)]
|
||||
@@ -1277,7 +1277,7 @@ def makeMOBIWorker(item):
|
||||
kindlegenError = ''
|
||||
try:
|
||||
if os.path.getsize(item) < 629145600:
|
||||
output = subprocess_run_silent(['kindlegen', '-dont_append_source', '-locale', 'en', item],
|
||||
output = subprocess_run(['kindlegen', '-dont_append_source', '-locale', 'en', item],
|
||||
stdout=PIPE, stderr=STDOUT, encoding='UTF-8')
|
||||
for line in output.stdout.splitlines():
|
||||
# ERROR: Generic error
|
||||
|
||||
@@ -18,15 +18,14 @@
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
|
||||
from functools import cached_property
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import distro
|
||||
from shutil import move
|
||||
from subprocess import STDOUT, PIPE, CalledProcessError
|
||||
from xml.dom.minidom import parseString
|
||||
from xml.parsers.expat import ExpatError
|
||||
from .shared import subprocess_run_silent
|
||||
from .shared import subprocess_run
|
||||
|
||||
EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.'
|
||||
|
||||
@@ -34,56 +33,78 @@ EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KC
|
||||
class ComicArchive:
|
||||
def __init__(self, filepath):
|
||||
self.filepath = filepath
|
||||
self.type = None
|
||||
if not os.path.isfile(self.filepath):
|
||||
raise OSError('File not found.')
|
||||
try:
|
||||
process = subprocess_run_silent(['7z', 'l', '-y', '-p1', self.filepath], stderr=STDOUT, stdout=PIPE)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
for line in process.stdout.splitlines():
|
||||
if b'Type =' in line:
|
||||
self.type = line.rstrip().decode().split(' = ')[1].upper()
|
||||
break
|
||||
if process.returncode != 0 and distro.id() == 'fedora':
|
||||
process = subprocess_run_silent(['unrar', 'l', '-y', '-p1', self.filepath], stderr=STDOUT, stdout=PIPE)
|
||||
for line in process.stdout.splitlines():
|
||||
if b'Details: ' in line:
|
||||
self.type = line.rstrip().decode().split(' ')[1].upper()
|
||||
break
|
||||
if process.returncode != 0:
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
|
||||
@cached_property
|
||||
def type(self):
|
||||
extraction_commands = [
|
||||
['7z', 'l', '-y', '-p1', self.filepath],
|
||||
]
|
||||
|
||||
if distro.id() == 'fedora':
|
||||
extraction_commands.append(
|
||||
['unrar', 'l', '-y', '-p1', self.filepath],
|
||||
)
|
||||
|
||||
for cmd in extraction_commands:
|
||||
try:
|
||||
process = subprocess_run(cmd, capture_output=True, check=True)
|
||||
for line in process.stdout.splitlines():
|
||||
if b'Type =' in line:
|
||||
return line.rstrip().decode().split(' = ')[1].upper()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except CalledProcessError:
|
||||
pass
|
||||
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
|
||||
def extract(self, targetdir):
|
||||
if not os.path.isdir(targetdir):
|
||||
raise OSError('Target directory doesn\'t exist.')
|
||||
try:
|
||||
process = subprocess_run_silent(['tar', '-xf', self.filepath, '-C', targetdir],
|
||||
stdout=PIPE, stderr=STDOUT, check=True)
|
||||
return targetdir
|
||||
except (FileNotFoundError, CalledProcessError):
|
||||
pass
|
||||
process = subprocess_run_silent(['7z', 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.filepath],
|
||||
stdout=PIPE, stderr=STDOUT)
|
||||
if process.returncode != 0 and distro.id() == 'fedora':
|
||||
process = subprocess_run_silent(['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.filepath, targetdir]
|
||||
, stdout=PIPE, stderr=STDOUT)
|
||||
if process.returncode != 0:
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
elif process.returncode != 0:
|
||||
|
||||
missing = []
|
||||
|
||||
extraction_commands = [
|
||||
['tar', '--exclude', '__MACOSX', '--exclude', '.DS_Store', '--exclude', 'thumbs.db', '--exclude', 'Thumbs.db', '-xf', self.filepath, '-C', targetdir],
|
||||
['7z', 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.filepath],
|
||||
]
|
||||
|
||||
if platform.system() == 'Darwin':
|
||||
extraction_commands.append(
|
||||
['unar', self.filepath, '-f', '-o', targetdir]
|
||||
)
|
||||
|
||||
if distro.id() == 'fedora':
|
||||
extraction_commands.append(
|
||||
['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.filepath, targetdir]
|
||||
)
|
||||
|
||||
for cmd in extraction_commands:
|
||||
try:
|
||||
subprocess_run(cmd, capture_output=True, check=True)
|
||||
return targetdir
|
||||
except FileNotFoundError:
|
||||
missing.append(cmd[0])
|
||||
except CalledProcessError:
|
||||
pass
|
||||
|
||||
if missing:
|
||||
raise OSError(f'Extraction failed, install <a href="https://github.com/ciromattia/kcc#7-zip">specialized extraction software.</a> ')
|
||||
else:
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
return targetdir
|
||||
|
||||
def addFile(self, sourcefile):
|
||||
if self.type in ['RAR', 'RAR5']:
|
||||
raise NotImplementedError
|
||||
process = subprocess_run_silent(['7z', 'a', '-y', self.filepath, sourcefile],
|
||||
process = subprocess_run(['7z', 'a', '-y', self.filepath, sourcefile],
|
||||
stdout=PIPE, stderr=STDOUT)
|
||||
if process.returncode != 0:
|
||||
raise OSError('Failed to add the file.')
|
||||
|
||||
def extractMetadata(self):
|
||||
process = subprocess_run_silent(['7z', 'x', '-y', '-so', self.filepath, 'ComicInfo.xml'],
|
||||
process = subprocess_run(['7z', 'x', '-y', '-so', self.filepath, 'ComicInfo.xml'],
|
||||
stdout=PIPE, stderr=STDOUT)
|
||||
if process.returncode != 0:
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
|
||||
@@ -136,7 +136,11 @@ def del_exth(rec0, exth_num):
|
||||
|
||||
|
||||
class DualMobiMetaFix:
|
||||
def __init__(self, infile, outfile, asin):
|
||||
def __init__(self, infile, outfile, asin, is_pdoc):
|
||||
cdetype = b'EBOK'
|
||||
if is_pdoc:
|
||||
cdetype = b'PDOC'
|
||||
|
||||
shutil.copyfile(infile, outfile)
|
||||
f = open(outfile, "r+b")
|
||||
self.datain = mmap.mmap(f.fileno(), 0)
|
||||
@@ -147,7 +151,7 @@ class DualMobiMetaFix:
|
||||
rec0 = self.datain_rec0
|
||||
rec0 = del_exth(rec0, 501)
|
||||
rec0 = del_exth(rec0, 113)
|
||||
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||
rec0 = add_exth(rec0, 501, cdetype)
|
||||
rec0 = add_exth(rec0, 113, asin)
|
||||
replacesection(self.datain, 0, rec0)
|
||||
|
||||
@@ -174,7 +178,7 @@ class DualMobiMetaFix:
|
||||
rec0 = self.datain_kfrec0
|
||||
rec0 = del_exth(rec0, 501)
|
||||
rec0 = del_exth(rec0, 113)
|
||||
rec0 = add_exth(rec0, 501, b'EBOK')
|
||||
rec0 = add_exth(rec0, 501, cdetype)
|
||||
rec0 = add_exth(rec0, 113, asin)
|
||||
replacesection(self.datain, datain_kf8, rec0)
|
||||
|
||||
|
||||
@@ -78,18 +78,29 @@ class ProfileData:
|
||||
PalleteNull = [
|
||||
]
|
||||
|
||||
Profiles = {
|
||||
ProfilesKindleEBOK = {
|
||||
'K1': ("Kindle 1", (600, 670), Palette4, 1.8),
|
||||
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.8),
|
||||
'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
|
||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
|
||||
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.8),
|
||||
'K578': ("Kindle", (600, 800), Palette16, 1.8),
|
||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
|
||||
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
|
||||
'KV': ("Kindle Paperwhite 3/4/Voyage/Oasis", (1072, 1448), Palette16, 1.8),
|
||||
}
|
||||
|
||||
ProfilesKindlePDOC = {
|
||||
'KO': ("Kindle Oasis 2/3/Paperwhite 12/Colorsoft 12", (1264, 1680), Palette16, 1.8),
|
||||
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.8),
|
||||
'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.8),
|
||||
'KO': ("Kindle Oasis 2/3", (1264, 1680), Palette16, 1.8),
|
||||
'KS': ("Kindle Scribe", (1860, 2480), Palette16, 1.8),
|
||||
}
|
||||
|
||||
ProfilesKindle = {
|
||||
**ProfilesKindleEBOK,
|
||||
**ProfilesKindlePDOC
|
||||
}
|
||||
|
||||
ProfilesKobo = {
|
||||
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
|
||||
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
|
||||
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8),
|
||||
@@ -105,6 +116,11 @@ class ProfileData:
|
||||
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8),
|
||||
'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.8),
|
||||
'KoE': ("Kobo Elipsa", (1404, 1872), Palette16, 1.8),
|
||||
}
|
||||
|
||||
Profiles = {
|
||||
**ProfilesKindle,
|
||||
**ProfilesKobo,
|
||||
'OTHER': ("Other", (0, 0), Palette16, 1.8),
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
import os.path
|
||||
import psutil
|
||||
|
||||
from . import image
|
||||
|
||||
|
||||
class Kindle:
|
||||
def __init__(self):
|
||||
def __init__(self, profile):
|
||||
self.profile = profile
|
||||
self.path = self.findDevice()
|
||||
if self.path:
|
||||
self.coverSupport = self.checkThumbnails()
|
||||
@@ -29,9 +32,11 @@ class Kindle:
|
||||
self.coverSupport = False
|
||||
|
||||
def findDevice(self):
|
||||
if self.profile in image.ProfileData.ProfilesKindlePDOC.keys():
|
||||
return False
|
||||
for drive in reversed(psutil.disk_partitions(False)):
|
||||
if (drive[2] == 'FAT32' and drive[3] == 'rw,removable') or \
|
||||
(drive[2] in ('vfat', 'msdos', 'FAT') and 'rw' in drive[3]):
|
||||
(drive[2] in ('vfat', 'msdos', 'FAT', 'apfs') and 'rw' in drive[3]):
|
||||
if os.path.isdir(os.path.join(drive[1], 'system')) and \
|
||||
os.path.isdir(os.path.join(drive[1], 'documents')):
|
||||
return drive[1]
|
||||
|
||||
@@ -22,7 +22,7 @@ import os
|
||||
from hashlib import md5
|
||||
from html.parser import HTMLParser
|
||||
import subprocess
|
||||
from distutils.version import StrictVersion
|
||||
from packaging.version import Version
|
||||
from re import split
|
||||
import sys
|
||||
from traceback import format_tb
|
||||
@@ -103,7 +103,7 @@ def dependencyCheck(level):
|
||||
if level > 2:
|
||||
try:
|
||||
from PySide6.QtCore import qVersion as qtVersion
|
||||
if StrictVersion('6.5.1') > StrictVersion(qtVersion()):
|
||||
if Version('6.5.1') > Version(qtVersion()):
|
||||
missing.append('PySide 6.5.1+')
|
||||
except ImportError:
|
||||
missing.append('PySide 6.5.1+')
|
||||
@@ -114,7 +114,7 @@ def dependencyCheck(level):
|
||||
if level > 1:
|
||||
try:
|
||||
from psutil import __version__ as psutilVersion
|
||||
if StrictVersion('5.0.0') > StrictVersion(psutilVersion):
|
||||
if Version('5.0.0') > Version(psutilVersion):
|
||||
missing.append('psutil 5.0.0+')
|
||||
except ImportError:
|
||||
missing.append('psutil 5.0.0+')
|
||||
@@ -123,13 +123,13 @@ def dependencyCheck(level):
|
||||
from slugify import __version__ as slugifyVersion
|
||||
if isinstance(slugifyVersion, ModuleType):
|
||||
slugifyVersion = slugifyVersion.__version__
|
||||
if StrictVersion('1.2.1') > StrictVersion(slugifyVersion):
|
||||
if Version('1.2.1') > Version(slugifyVersion):
|
||||
missing.append('python-slugify 1.2.1+')
|
||||
except ImportError:
|
||||
missing.append('python-slugify 1.2.1+')
|
||||
try:
|
||||
from PIL import __version__ as pillowVersion
|
||||
if StrictVersion('5.2.0') > StrictVersion(pillowVersion):
|
||||
if Version('5.2.0') > Version(pillowVersion):
|
||||
missing.append('Pillow 5.2.0+')
|
||||
except ImportError:
|
||||
missing.append('Pillow 5.2.0+')
|
||||
@@ -137,7 +137,7 @@ def dependencyCheck(level):
|
||||
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||
sys.exit(1)
|
||||
|
||||
def subprocess_run_silent(command, **kwargs):
|
||||
def subprocess_run(command, **kwargs):
|
||||
if (os.name == 'nt'):
|
||||
kwargs.setdefault('creationflags', subprocess.CREATE_NO_WINDOW)
|
||||
return subprocess.run(command, **kwargs)
|
||||
|
||||
@@ -4,6 +4,7 @@ psutil>=5.9.5
|
||||
requests>=2.31.0
|
||||
python-slugify>=1.2.1
|
||||
raven>=6.0.0
|
||||
packaging>=23.2
|
||||
mozjpeg-lossless-optimization>=1.1.2
|
||||
natsort[fast]>=8.4.0
|
||||
distro>=1.8.0
|
||||
|
||||
9
setup.py
9
setup.py
@@ -14,7 +14,6 @@ import os
|
||||
import platform
|
||||
import sys
|
||||
import setuptools
|
||||
import distutils.cmd
|
||||
from kindlecomicconverter import __version__
|
||||
|
||||
NAME = 'KindleComicConverter'
|
||||
@@ -23,7 +22,7 @@ VERSION = __version__
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
class BuildBinaryCommand(distutils.cmd.Command):
|
||||
class BuildBinaryCommand(setuptools.Command):
|
||||
description = 'build binary release'
|
||||
user_options = []
|
||||
|
||||
@@ -37,16 +36,16 @@ class BuildBinaryCommand(distutils.cmd.Command):
|
||||
def run(self):
|
||||
VERSION = __version__
|
||||
if sys.platform == 'darwin':
|
||||
os.system('pyinstaller -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
|
||||
os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
|
||||
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
|
||||
os.system(f'appdmg kcc.json dist/kcc_macos_{platform.processor()}_{VERSION}.dmg')
|
||||
sys.exit(0)
|
||||
elif sys.platform == 'win32':
|
||||
os.system('pyinstaller -y -F -i icons\\comic2ebook.ico -n KCC_' + VERSION + ' -w --noupx kcc.py')
|
||||
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n KCC_' + VERSION + ' -w --noupx kcc.py')
|
||||
sys.exit(0)
|
||||
elif sys.platform == 'linux':
|
||||
os.system(
|
||||
'pyinstaller --hidden-import=queue -y -F -i icons/comic2ebook.ico -n kcc_linux_' + VERSION + ' kcc.py')
|
||||
'pyinstaller --hidden-import=_cffi_backend --hidden-import=queue -y -F -i icons/comic2ebook.ico -n kcc_linux_' + VERSION + ' kcc.py')
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
Reference in New Issue
Block a user