mirror of
https://github.com/ciromattia/kcc
synced 2026-04-18 23:19:00 +00:00
Compare commits
28 Commits
v7.4.1
...
clone-fork
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa9ee43278 | ||
|
|
c4f845c221 | ||
|
|
ebb59dbc2d | ||
|
|
8da2b4cb96 | ||
|
|
7b8858678f | ||
|
|
be07e0df6a | ||
|
|
dc711e671d | ||
|
|
581ecd0ec2 | ||
|
|
f3a32c6174 | ||
|
|
0ce5f7f186 | ||
|
|
c3d2f89471 | ||
|
|
b1c4cd36f1 | ||
|
|
b7c6281b55 | ||
|
|
559485184d | ||
|
|
75f5274449 | ||
|
|
dfc149d893 | ||
|
|
327b522080 | ||
|
|
7c029b4ba1 | ||
|
|
2db8f5c8fd | ||
|
|
ce72921289 | ||
|
|
f1bbc47798 | ||
|
|
5a39007db6 | ||
|
|
0d7487f8d4 | ||
|
|
8a78f82ff2 | ||
|
|
149f7e5921 | ||
|
|
40d219de4d | ||
|
|
370d1d7392 | ||
|
|
8295f163c2 |
1
.github/workflows/package-linux.yml
vendored
1
.github/workflows/package-linux.yml
vendored
@@ -70,6 +70,5 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
CHANGELOG.md
|
|
||||||
LICENSE.txt
|
LICENSE.txt
|
||||||
*.AppImage*
|
*.AppImage*
|
||||||
|
|||||||
1
.github/workflows/package-macos.yml
vendored
1
.github/workflows/package-macos.yml
vendored
@@ -89,7 +89,6 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
CHANGELOG.md
|
|
||||||
LICENSE.txt
|
LICENSE.txt
|
||||||
dist/*.dmg
|
dist/*.dmg
|
||||||
- name: Clean up keychain and provisioning profile
|
- name: Clean up keychain and provisioning profile
|
||||||
|
|||||||
@@ -10,46 +10,37 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
entry: [ kcc-c2e, kcc-c2p ]
|
||||||
|
include:
|
||||||
|
- entry: kcc-c2e
|
||||||
|
capital: KCC_c2e
|
||||||
|
- entry: kcc-c2p
|
||||||
|
capital: KCC_c2p
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
# - name: Set up Python
|
|
||||||
# uses: actions/setup-python@v4
|
|
||||||
# with:
|
|
||||||
# python-version: 3.11
|
|
||||||
# cache: 'pip'
|
|
||||||
# - name: Install python dependencies
|
|
||||||
# run: |
|
|
||||||
# python -m pip install --upgrade pip setuptools wheel pyinstaller
|
|
||||||
# pip install -r requirements.txt
|
|
||||||
# - name: build binary
|
|
||||||
# run: |
|
|
||||||
# pyi-makespec -F -i icons\\comic2ebook.ico -n KCC_test -w --noupx kcc.py
|
|
||||||
- name: Package Application
|
- name: Package Application
|
||||||
uses: JackMcKew/pyinstaller-action-windows@main
|
uses: JackMcKew/pyinstaller-action-windows@main
|
||||||
with:
|
with:
|
||||||
path: .
|
path: .
|
||||||
spec: ./kcc-c2e.spec
|
spec: ./${{ matrix.entry }}.spec
|
||||||
- name: Package Application
|
|
||||||
uses: JackMcKew/pyinstaller-action-windows@main
|
|
||||||
with:
|
|
||||||
path: .
|
|
||||||
spec: ./kcc-c2p.spec
|
|
||||||
- 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-c2e.exe dist/windows/KCC_c2e_${version_built}.exe
|
mv dist/windows/${{ matrix.entry }}.exe dist/windows/${{ matrix.capital }}_${version_built}.exe
|
||||||
mv dist/windows/kcc-c2p.exe dist/windows/KCC_c2p_${version_built}.exe
|
|
||||||
|
|
||||||
- name: upload-unsigned-artifact
|
- name: upload-unsigned-artifact
|
||||||
id: upload-unsigned-artifact
|
id: upload-unsigned-artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-build
|
name: windows-build-${{ matrix.entry }}
|
||||||
path: dist/windows/*.exe
|
path: dist/windows/*.exe
|
||||||
|
|
||||||
- id: optional_step_id
|
- id: optional_step_id
|
||||||
uses: signpath/github-action-submit-signing-request@v1.1
|
uses: signpath/github-action-submit-signing-request@v1.2
|
||||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||||
with:
|
with:
|
||||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||||
|
|||||||
2
.github/workflows/package-windows.yml
vendored
2
.github/workflows/package-windows.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
|||||||
name: windows-build
|
name: windows-build
|
||||||
path: dist/*.exe
|
path: dist/*.exe
|
||||||
- id: optional_step_id
|
- id: optional_step_id
|
||||||
uses: signpath/github-action-submit-signing-request@v1.1
|
uses: signpath/github-action-submit-signing-request@v1.2
|
||||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||||
with:
|
with:
|
||||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -1,3 +1,5 @@
|
|||||||
|
<img src="header.jpg" alt="Header Image" width="400">
|
||||||
|
|
||||||
# KCC
|
# KCC
|
||||||
|
|
||||||
|
|
||||||
@@ -6,12 +8,13 @@
|
|||||||
[](https://github.com/ciromattia/kcc/pkgs/container/kcc)
|
[](https://github.com/ciromattia/kcc/pkgs/container/kcc)
|
||||||
|
|
||||||
**Kindle Comic Converter** optimizes comics and manga for eink readers like Kindle, Kobo, ReMarkable, and more.
|
**Kindle Comic Converter** optimizes comics and manga for eink readers like Kindle, Kobo, ReMarkable, and more.
|
||||||
|
Pages display in fullscreen without margins, with proper fixed layout support.
|
||||||
Its main feature is various optional image processing steps to look good on eink screens,
|
Its main feature is various optional image processing steps to look good on eink screens,
|
||||||
which have different requirements than normal LCD screens.
|
which have different requirements than normal LCD screens.
|
||||||
It also does filesize optimization by downscaling to your specific device's screen resolution,
|
It also does filesize optimization by downscaling to your specific device's screen resolution,
|
||||||
which can improve performance on underpowered ereaders.
|
which can improve performance on underpowered ereaders.
|
||||||
Supported input formats include folders/CBZ/CBR/PDF of JPG/PNG files and more.
|
Supported input formats include folders and archives of JPG/PNG files and more.
|
||||||
Supported output formats include MOBI/AZW3, EPUB, KEPUB, and CBZ.
|
Supported output formats include virtual panel view MOBI/AZW3, EPUB, KEPUB, and CBZ.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -74,6 +77,8 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
|
|||||||
- Cannot connect Kindle Scribe or 2024+ Kindle to macOS
|
- Cannot connect Kindle Scribe or 2024+ Kindle to macOS
|
||||||
- Use official MTP [Amazon USB File Transfer app](https://www.amazon.com/gp/help/customer/display.html/ref=hp_Connect_USB_MTP?nodeId=TCUBEdEkbIhK07ysFu)
|
- Use official MTP [Amazon USB File Transfer app](https://www.amazon.com/gp/help/customer/display.html/ref=hp_Connect_USB_MTP?nodeId=TCUBEdEkbIhK07ysFu)
|
||||||
(no login required). Works much better than previously recommended Android File Transfer. Cannot run simutaneously with other transfer apps.
|
(no login required). Works much better than previously recommended Android File Transfer. Cannot run simutaneously with other transfer apps.
|
||||||
|
- Huge margins / slow page turns?
|
||||||
|
- You likely modified the file during transfer using a 3rd party app. Try simply dragging and dropping the final mobi/kepub file into the Kindle documents folder via USB.
|
||||||
|
|
||||||
## PREREQUISITES
|
## PREREQUISITES
|
||||||
|
|
||||||
@@ -245,13 +250,13 @@ OTHER:
|
|||||||
|
|
||||||
This section is for developers who want to contribute to KCC or power users who want to run the latest code without waiting for an official release.
|
This section is for developers who want to contribute to KCC or power users who want to run the latest code without waiting for an official release.
|
||||||
|
|
||||||
Easiest to use [GitHub Desktop](https://desktop.github.com) to clone the KCC repo. From GitHub Desktop, click on `Repository` in the toolbar, then `Command Prompt` (Windows)/`Terminal` (Mac) to open a window in the KCC repo.
|
Easiest to use [GitHub Desktop](https://desktop.github.com) to clone your fork of the KCC repo. From GitHub Desktop, click on `Repository` in the toolbar, then `Command Prompt` (Windows)/`Terminal` (Mac) to open a window in the KCC repo.
|
||||||
|
|
||||||
Depending on your system [Python](https://www.python.org) may be called either `python` or `python3`. We use virtual environments (venv) to manage dependencies.
|
Depending on your system [Python](https://www.python.org) may be called either `python` or `python3`. We use virtual environments (venv) to manage dependencies.
|
||||||
|
|
||||||
If you want to edit the code, a good code editor is [VS Code](https://code.visualstudio.com).
|
If you want to edit the code, a good code editor is [VS Code](https://code.visualstudio.com).
|
||||||
|
|
||||||
If you want to edit the `.ui` files, use [Qt Creator](https://www.qt.io/download-qt-installer-oss), included in **Qt for desktop development**.
|
If you want to edit the `.ui` files, use `pyside6-designer` which is included in the `pip install pyside6`.
|
||||||
Then use the `gen_ui_files` scripts to autogenerate the python UI.
|
Then use the `gen_ui_files` scripts to autogenerate the python UI.
|
||||||
|
|
||||||
An example PR adding a new checkbox is here: https://github.com/ciromattia/kcc/pull/785
|
An example PR adding a new checkbox is here: https://github.com/ciromattia/kcc/pull/785
|
||||||
@@ -323,6 +328,12 @@ The app relies and includes the following scripts:
|
|||||||
- Icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) License.
|
- Icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) License.
|
||||||
|
|
||||||
## SAMPLE FILES CREATED BY KCC
|
## SAMPLE FILES CREATED BY KCC
|
||||||
|
|
||||||
|
https://www.mediafire.com/folder/ixh40veo6hrc5/kcc_samples
|
||||||
|
|
||||||
|
Older links (dead):
|
||||||
|
|
||||||
|
|
||||||
* [Kindle Oasis 2 / 3](http://kcc.iosphe.re/Samples/Ubunchu!-KO.mobi)
|
* [Kindle Oasis 2 / 3](http://kcc.iosphe.re/Samples/Ubunchu!-KO.mobi)
|
||||||
* [Kindle Paperwhite 3 / 4 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
|
* [Kindle Paperwhite 3 / 4 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi)
|
||||||
* [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
|
* [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi)
|
||||||
|
|||||||
BIN
header.jpg
Normal file
BIN
header.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 921 KiB |
@@ -424,6 +424,8 @@ class WorkerThread(QThread):
|
|||||||
MW.addMessage.emit('Created EPUB file was too big.', 'error', False)
|
MW.addMessage.emit('Created EPUB file was too big.', 'error', False)
|
||||||
MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error',
|
MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error',
|
||||||
False)
|
False)
|
||||||
|
if self.kindlegenErrorCode[0] == 3221226505:
|
||||||
|
MW.addMessage.emit('Unknown Windows error. Possibly filepath too long?', 'error', False)
|
||||||
else:
|
else:
|
||||||
for item in outputPath:
|
for item in outputPath:
|
||||||
if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(item):
|
if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(item):
|
||||||
@@ -878,7 +880,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
versionCheck = subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, encoding='UTF-8')
|
versionCheck = subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, encoding='UTF-8', errors='ignore', check=True)
|
||||||
self.kindleGen = True
|
self.kindleGen = True
|
||||||
for line in versionCheck.stdout.splitlines():
|
for line in versionCheck.stdout.splitlines():
|
||||||
if 'Amazon kindlegen' in line:
|
if 'Amazon kindlegen' in line:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from uuid import uuid4
|
|||||||
from natsort import os_sort_keygen
|
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, CalledProcessError
|
||||||
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
|
||||||
|
|
||||||
@@ -285,9 +285,9 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
|||||||
"<dc:identifier id=\"BookID\">urn:uuid:", options.uuid, "</dc:identifier>\n",
|
"<dc:identifier id=\"BookID\">urn:uuid:", options.uuid, "</dc:identifier>\n",
|
||||||
"<dc:contributor id=\"contributor\">KindleComicConverter-" + __version__ + "</dc:contributor>\n"])
|
"<dc:contributor id=\"contributor\">KindleComicConverter-" + __version__ + "</dc:contributor>\n"])
|
||||||
if len(options.summary) > 0:
|
if len(options.summary) > 0:
|
||||||
f.writelines(["<dc:description>", options.summary, "</dc:description>\n"])
|
f.writelines(["<dc:description>", hescape(options.summary), "</dc:description>\n"])
|
||||||
for author in options.authors:
|
for author in options.authors:
|
||||||
f.writelines(["<dc:creator>", author, "</dc:creator>\n"])
|
f.writelines(["<dc:creator>", hescape(author), "</dc:creator>\n"])
|
||||||
f.writelines(["<meta property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\n",
|
f.writelines(["<meta property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\n",
|
||||||
"<meta name=\"cover\" content=\"cover\"/>\n"])
|
"<meta name=\"cover\" content=\"cover\"/>\n"])
|
||||||
if options.iskindle and options.profile != 'Custom':
|
if options.iskindle and options.profile != 'Custom':
|
||||||
@@ -513,8 +513,11 @@ def buildEPUB(path, chapternames, tomenumber, ischunked):
|
|||||||
dirnames, filenames = walkSort(dirnames, filenames)
|
dirnames, filenames = walkSort(dirnames, filenames)
|
||||||
for afile in filenames:
|
for afile in filenames:
|
||||||
if cover is None:
|
if cover is None:
|
||||||
cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'),
|
try:
|
||||||
'cover' + getImageFileName(afile)[1])
|
cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'),
|
||||||
|
'cover' + getImageFileName(afile)[1])
|
||||||
|
except Exception as e:
|
||||||
|
raise UserWarning(f"{afile}: {e}")
|
||||||
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))
|
||||||
if not chapter:
|
if not chapter:
|
||||||
@@ -579,6 +582,10 @@ def imgDirectoryProcessing(path):
|
|||||||
workerPool.join()
|
workerPool.join()
|
||||||
img_processing_end = perf_counter()
|
img_processing_end = perf_counter()
|
||||||
print(f"imgFileProcessing: {img_processing_end - img_processing_start} seconds")
|
print(f"imgFileProcessing: {img_processing_end - img_processing_start} seconds")
|
||||||
|
|
||||||
|
# macOS 15 likes to add ._ files after multiprocessing
|
||||||
|
dot_clean(path)
|
||||||
|
|
||||||
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.")
|
||||||
@@ -743,7 +750,7 @@ def getComicInfo(path, originalpath):
|
|||||||
return
|
return
|
||||||
if defaultTitle:
|
if defaultTitle:
|
||||||
if xml.data['Series']:
|
if xml.data['Series']:
|
||||||
options.title = hescape(xml.data['Series'])
|
options.title = xml.data['Series']
|
||||||
if xml.data['Volume']:
|
if xml.data['Volume']:
|
||||||
titleSuffix += ' V' + xml.data['Volume'].zfill(2)
|
titleSuffix += ' V' + xml.data['Volume'].zfill(2)
|
||||||
if xml.data['Number']:
|
if xml.data['Number']:
|
||||||
@@ -753,7 +760,7 @@ def getComicInfo(path, originalpath):
|
|||||||
options.authors = []
|
options.authors = []
|
||||||
for field in ['Writers', 'Pencillers', 'Inkers', 'Colorists']:
|
for field in ['Writers', 'Pencillers', 'Inkers', 'Colorists']:
|
||||||
for person in xml.data[field]:
|
for person in xml.data[field]:
|
||||||
options.authors.append(hescape(person))
|
options.authors.append(person)
|
||||||
if len(options.authors) > 0:
|
if len(options.authors) > 0:
|
||||||
options.authors = list(set(options.authors))
|
options.authors = list(set(options.authors))
|
||||||
options.authors.sort()
|
options.authors.sort()
|
||||||
@@ -762,7 +769,7 @@ def getComicInfo(path, originalpath):
|
|||||||
if xml.data['Bookmarks']:
|
if xml.data['Bookmarks']:
|
||||||
options.comicinfo_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 = xml.data['Summary']
|
||||||
os.remove(xmlPath)
|
os.remove(xmlPath)
|
||||||
|
|
||||||
|
|
||||||
@@ -836,12 +843,25 @@ def sanitizePermissions(filetree):
|
|||||||
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD)
|
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD)
|
||||||
for name in dirs:
|
for name in dirs:
|
||||||
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC)
|
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC)
|
||||||
|
# clean dot from original file
|
||||||
|
dot_clean(filetree)
|
||||||
|
|
||||||
|
def dot_clean(filetree):
|
||||||
|
for root, _, files in os.walk(filetree, topdown=False):
|
||||||
|
for name in files:
|
||||||
|
if name.startswith('._'):
|
||||||
|
os.remove(os.path.join(root, name))
|
||||||
|
|
||||||
|
|
||||||
def chunk_directory(path):
|
def chunk_directory(path):
|
||||||
level = -1
|
level = -1
|
||||||
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||||
for f in files:
|
for f in files:
|
||||||
|
# Windows MAX_LENGTH = 260 plus some buffer
|
||||||
|
if len(os.path.join(root, f)) > 180:
|
||||||
|
flattenTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||||
|
level = 1
|
||||||
|
break
|
||||||
if getImageFileName(f):
|
if getImageFileName(f):
|
||||||
newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep)
|
newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep)
|
||||||
if level != -1 and level != newLevel:
|
if level != -1 and level != newLevel:
|
||||||
@@ -935,7 +955,8 @@ def detectSuboptimalProcessing(tmppath, orgpath):
|
|||||||
raise RuntimeError('Image file %s is corrupted. Error: %s' % (pathOrg, str(err)))
|
raise RuntimeError('Image file %s is corrupted. Error: %s' % (pathOrg, str(err)))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
os.remove(os.path.join(root, name))
|
if os.path.exists(os.path.join(root, name)):
|
||||||
|
os.remove(os.path.join(root, name))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise RuntimeError(f"{name}: {e}")
|
raise RuntimeError(f"{name}: {e}")
|
||||||
# remove empty nested folders
|
# remove empty nested folders
|
||||||
@@ -1182,7 +1203,7 @@ def checkTools(source):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if options.format == 'MOBI':
|
if options.format == 'MOBI':
|
||||||
try:
|
try:
|
||||||
subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT)
|
subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, check=True)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print('ERROR: KindleGen is missing!')
|
print('ERROR: KindleGen is missing!')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -1345,27 +1366,28 @@ def makeMOBIWorker(item):
|
|||||||
try:
|
try:
|
||||||
if os.path.getsize(item) < 629145600:
|
if os.path.getsize(item) < 629145600:
|
||||||
output = subprocess_run(['kindlegen', '-dont_append_source', '-locale', 'en', item],
|
output = subprocess_run(['kindlegen', '-dont_append_source', '-locale', 'en', item],
|
||||||
stdout=PIPE, stderr=STDOUT, encoding='UTF-8')
|
stdout=PIPE, stderr=STDOUT, encoding='UTF-8', errors='ignore', check=True)
|
||||||
for line in output.stdout.splitlines():
|
|
||||||
# ERROR: Generic error
|
|
||||||
if "Error(" in line:
|
|
||||||
kindlegenErrorCode = 1
|
|
||||||
kindlegenError = line
|
|
||||||
# ERROR: EPUB too big
|
|
||||||
if ":E23026:" in line:
|
|
||||||
kindlegenErrorCode = 23026
|
|
||||||
if kindlegenErrorCode > 0:
|
|
||||||
break
|
|
||||||
if ":I1036: Mobi file built successfully" in line:
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
# ERROR: EPUB too big
|
# ERROR: EPUB too big
|
||||||
kindlegenErrorCode = 23026
|
kindlegenErrorCode = 23026
|
||||||
return [kindlegenErrorCode, kindlegenError, item]
|
return [kindlegenErrorCode, kindlegenError, item]
|
||||||
except Exception as err:
|
except CalledProcessError as err:
|
||||||
|
for line in err.stdout.splitlines():
|
||||||
|
# ERROR: Generic error
|
||||||
|
if "Error(" in line:
|
||||||
|
kindlegenErrorCode = 1
|
||||||
|
kindlegenError = line
|
||||||
|
# ERROR: EPUB too big
|
||||||
|
if ":E23026:" in line:
|
||||||
|
kindlegenErrorCode = 23026
|
||||||
|
if kindlegenErrorCode > 0:
|
||||||
|
break
|
||||||
|
if ":I1036: Mobi file built successfully" in line:
|
||||||
|
break
|
||||||
# ERROR: KCC unknown generic error
|
# ERROR: KCC unknown generic error
|
||||||
kindlegenErrorCode = 1
|
if kindlegenErrorCode == 0:
|
||||||
kindlegenError = format(err)
|
kindlegenErrorCode = err.returncode
|
||||||
|
kindlegenError = err.stdout
|
||||||
return [kindlegenErrorCode, kindlegenError, item]
|
return [kindlegenErrorCode, kindlegenError, item]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -368,16 +368,11 @@ class ComicPage:
|
|||||||
if self.opt.stretch:
|
if self.opt.stretch:
|
||||||
self.image = self.image.resize(self.size, method)
|
self.image = self.image.resize(self.size, method)
|
||||||
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
||||||
if self.opt.format == 'CBZ' or self.opt.kfx:
|
pass
|
||||||
borderw = int((self.size[0] - self.image.size[0]) / 2)
|
|
||||||
borderh = int((self.size[1] - self.image.size[1]) / 2)
|
|
||||||
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
|
|
||||||
if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]:
|
|
||||||
self.image = ImageOps.fit(self.image, self.size, method=method)
|
|
||||||
else: # if image bigger than device resolution or smaller with upscaling
|
else: # if image bigger than device resolution or smaller with upscaling
|
||||||
if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
||||||
self.image = ImageOps.fit(self.image, self.size, method=method)
|
self.image = ImageOps.fit(self.image, self.size, method=method)
|
||||||
elif self.opt.format == 'CBZ' or self.opt.kfx:
|
elif (self.opt.format == 'CBZ' or self.opt.kfx) and not self.opt.white_borders:
|
||||||
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
|
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
|
||||||
else:
|
else:
|
||||||
if self.kindle_scribe_azw3:
|
if self.kindle_scribe_azw3:
|
||||||
@@ -385,7 +380,7 @@ class ComicPage:
|
|||||||
self.image = ImageOps.contain(self.image, self.size, method=method)
|
self.image = ImageOps.contain(self.image, self.size, method=method)
|
||||||
|
|
||||||
def resize_method(self):
|
def resize_method(self):
|
||||||
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
if self.image.size[0] < self.size[0] and self.image.size[1] < self.size[1]:
|
||||||
return Image.Resampling.BICUBIC
|
return Image.Resampling.BICUBIC
|
||||||
else:
|
else:
|
||||||
return Image.Resampling.LANCZOS
|
return Image.Resampling.LANCZOS
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import os
|
|||||||
from xml.dom.minidom import parse, Document
|
from xml.dom.minidom import parse, Document
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
|
from xml.sax.saxutils import unescape
|
||||||
from . import comicarchive
|
from . import comicarchive
|
||||||
|
|
||||||
|
|
||||||
@@ -52,19 +53,19 @@ class MetadataParser:
|
|||||||
|
|
||||||
def parseXML(self):
|
def parseXML(self):
|
||||||
if len(self.rawdata.getElementsByTagName('Series')) != 0:
|
if len(self.rawdata.getElementsByTagName('Series')) != 0:
|
||||||
self.data['Series'] = self.rawdata.getElementsByTagName('Series')[0].firstChild.nodeValue
|
self.data['Series'] = unescape(self.rawdata.getElementsByTagName('Series')[0].firstChild.nodeValue)
|
||||||
if len(self.rawdata.getElementsByTagName('Volume')) != 0:
|
if len(self.rawdata.getElementsByTagName('Volume')) != 0:
|
||||||
self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue
|
self.data['Volume'] = self.rawdata.getElementsByTagName('Volume')[0].firstChild.nodeValue
|
||||||
if len(self.rawdata.getElementsByTagName('Number')) != 0:
|
if len(self.rawdata.getElementsByTagName('Number')) != 0:
|
||||||
self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue
|
self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue
|
||||||
if len(self.rawdata.getElementsByTagName('Summary')) != 0:
|
if len(self.rawdata.getElementsByTagName('Summary')) != 0:
|
||||||
self.data['Summary'] = self.rawdata.getElementsByTagName('Summary')[0].firstChild.nodeValue
|
self.data['Summary'] = unescape(self.rawdata.getElementsByTagName('Summary')[0].firstChild.nodeValue)
|
||||||
if len(self.rawdata.getElementsByTagName('Title')) != 0:
|
if len(self.rawdata.getElementsByTagName('Title')) != 0:
|
||||||
self.data['Title'] = self.rawdata.getElementsByTagName('Title')[0].firstChild.nodeValue
|
self.data['Title'] = unescape(self.rawdata.getElementsByTagName('Title')[0].firstChild.nodeValue)
|
||||||
for field in ['Writer', 'Penciller', 'Inker', 'Colorist']:
|
for field in ['Writer', 'Penciller', 'Inker', 'Colorist']:
|
||||||
if len(self.rawdata.getElementsByTagName(field)) != 0:
|
if len(self.rawdata.getElementsByTagName(field)) != 0:
|
||||||
for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '):
|
for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '):
|
||||||
self.data[field + 's'].append(person)
|
self.data[field + 's'].append(unescape(person))
|
||||||
self.data[field + 's'] = list(set(self.data[field + 's']))
|
self.data[field + 's'] = list(set(self.data[field + 's']))
|
||||||
self.data[field + 's'].sort()
|
self.data[field + 's'].sort()
|
||||||
if len(self.rawdata.getElementsByTagName('Page')) != 0:
|
if len(self.rawdata.getElementsByTagName('Page')) != 0:
|
||||||
|
|||||||
Reference in New Issue
Block a user