mirror of
https://github.com/ciromattia/kcc
synced 2026-04-23 09:28:59 +00:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a862f11ac | ||
|
|
c32620cfeb | ||
|
|
512cac7f8a | ||
|
|
5e86acc740 | ||
|
|
20388304e8 | ||
|
|
420bed995b | ||
|
|
bc92c2dd85 | ||
|
|
a1f4e040ba | ||
|
|
137d53672a | ||
|
|
0cc75ab1e7 | ||
|
|
4cecf6fc4d | ||
|
|
2f0c9ae95d | ||
|
|
b856a176b0 | ||
|
|
e50163fb59 | ||
|
|
0ab20a5ce3 | ||
|
|
9c69a6fdcc | ||
|
|
26f0bf9989 | ||
|
|
b3db3256d6 | ||
|
|
96a92fb9bb | ||
|
|
18f02df3a1 | ||
|
|
235db43fa6 | ||
|
|
cab7cae714 | ||
|
|
a41f947030 | ||
|
|
d2828877af | ||
|
|
704dcd6dbe | ||
|
|
6a7500441d | ||
|
|
753eeb64eb | ||
|
|
a63d9e3ad0 | ||
|
|
cc01dc611a | ||
|
|
9a605c2d8a | ||
|
|
a7005748c7 | ||
|
|
55193119fb | ||
|
|
741cab68f3 | ||
|
|
7f8f3e67b7 | ||
|
|
be12661f38 | ||
|
|
06e2ee2968 | ||
|
|
27296565a3 | ||
|
|
bb70337a35 | ||
|
|
cf0586ae70 | ||
|
|
4100141b46 | ||
|
|
8eb81b7d67 | ||
|
|
e2dbc05a83 | ||
|
|
50b82786a1 | ||
|
|
88d1643f64 | ||
|
|
ddf2fa360f | ||
|
|
43e974f20d |
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
4
.github/workflows/package-linux.yml
vendored
4
.github/workflows/package-linux.yml
vendored
@@ -25,9 +25,9 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11
|
||||
cache: 'pip'
|
||||
|
||||
6
.github/workflows/package-macos.yml
vendored
6
.github/workflows/package-macos.yml
vendored
@@ -28,9 +28,9 @@ jobs:
|
||||
os: [ macos-13, macos-14 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11
|
||||
cache: 'pip'
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
# apply provisioning profile
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 16
|
||||
- run: npm install -g appdmg
|
||||
|
||||
66
.github/workflows/package-osx-legacy.yml
vendored
Normal file
66
.github/workflows/package-osx-legacy.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: build KCC for osx legacy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
# Don't trigger if it's just a documentation update
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.MD'
|
||||
- '**.yml'
|
||||
- '**.sh'
|
||||
- 'docs/**'
|
||||
- 'Dockerfile'
|
||||
- 'LICENSE'
|
||||
- '.gitattributes'
|
||||
- '.gitignore'
|
||||
- '.dockerignore'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ macos-13 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
# We need the official Python, because the GA ones only support newer macOS versions
|
||||
# The deployment target is picked up by the Python build tools automatically
|
||||
PYTHON_VERSION: 3.11.9
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.14'
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Get Python
|
||||
run: curl https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg -o "python.pkg"
|
||||
- name: Install Python
|
||||
run: |
|
||||
sudo installer -pkg python.pkg -target /
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python3 --version
|
||||
pip3 install --upgrade pip setuptools wheel pyinstaller certifi
|
||||
pip3 install --upgrade -r requirements-osx-legacy.txt
|
||||
./gen_ui_files.sh
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 16
|
||||
- run: npm install -g appdmg
|
||||
- name: build binary
|
||||
run: |
|
||||
python3 setup.py build_binary
|
||||
- name: upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: osx-build-${{ runner.arch }}
|
||||
path: dist/*.dmg
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
LICENSE.txt
|
||||
dist/*.dmg
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
capital: KCC_c2p
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Package Application
|
||||
uses: JackMcKew/pyinstaller-action-windows@main
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
path: dist/windows/*.exe
|
||||
|
||||
- id: optional_step_id
|
||||
uses: signpath/github-action-submit-signing-request@v1.2
|
||||
uses: signpath/github-action-submit-signing-request@v1.3
|
||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
|
||||
6
.github/workflows/package-windows.yml
vendored
6
.github/workflows/package-windows.yml
vendored
@@ -25,9 +25,9 @@ jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.11
|
||||
cache: 'pip'
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
name: windows-build
|
||||
path: dist/*.exe
|
||||
- id: optional_step_id
|
||||
uses: signpath/github-action-submit-signing-request@v1.2
|
||||
uses: signpath/github-action-submit-signing-request@v1.3
|
||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
|
||||
61
.github/workflows/package-windows7.yml
vendored
Normal file
61
.github/workflows/package-windows7.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: build KCC for windows 7
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
# Don't trigger if it's just a documentation update
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.MD'
|
||||
- '**.yml'
|
||||
- '**.sh'
|
||||
- 'docs/**'
|
||||
- 'Dockerfile'
|
||||
- 'LICENSE'
|
||||
- '.gitattributes'
|
||||
- '.gitignore'
|
||||
- '.dockerignore'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
WINDOWS_7: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.8
|
||||
cache: 'pip'
|
||||
- name: Install dependencies
|
||||
env:
|
||||
PYINSTALLER_COMPILE_BOOTLOADER: 1
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
pip install -r requirements-win7.txt
|
||||
pip install certifi pyinstaller --no-binary pyinstaller
|
||||
.\gen_ui_files.bat
|
||||
- name: build binary
|
||||
run: |
|
||||
python setup.py build_binary
|
||||
- name: upload-unsigned-artifact
|
||||
id: upload-unsigned-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-build
|
||||
path: dist/*.exe
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
files: |
|
||||
LICENSE.txt
|
||||
dist/*.exe
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,6 +8,8 @@ dist/
|
||||
build/
|
||||
KindleComicConverter*.egg-info/
|
||||
.idea/
|
||||
win7
|
||||
osx10.11
|
||||
/venv/
|
||||
/kindlegen*
|
||||
/kcc.bat
|
||||
|
||||
@@ -126,7 +126,7 @@ RUN set -x && \
|
||||
# Install required python modules
|
||||
python -m pip install --upgrade pip && \
|
||||
python -m venv /opt/venv && \
|
||||
python -m pip install --upgrade pillow psutil requests python-slugify raven packaging mozjpeg-lossless-optimization natsort distro numpy
|
||||
python -m pip install --upgrade pillow psutil requests python-slugify raven packaging mozjpeg-lossless-optimization natsort distro numpy pymupdf
|
||||
|
||||
|
||||
######################################################################################
|
||||
|
||||
28
README.md
28
README.md
@@ -12,10 +12,14 @@ like Kindle, Kobo, ReMarkable, and more.
|
||||
Pages display in fullscreen without margins,
|
||||
with proper fixed layout support.
|
||||
Supported input formats include JPG/PNG/GIF image files in folders, archives, or PDFs.
|
||||
Supported output formats include MOBI/AZW3, EPUB, KEPUB, and CBZ.
|
||||
Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, and PDF.
|
||||
|
||||
If your source are super high resolution DRM-free PDFs from Kodansha/Humble Bundle/Fanatical,
|
||||
you'll need to first [convert the PDFs to CBZ](https://github.com/ciromattia/kcc/issues/680) for use in KCC.
|
||||
**NEW**: PDF output is now supported for direct conversion to reMarkable devices!
|
||||
When using a reMarkable profile (Rmk1, Rmk2, RmkPP), the format automatically defaults to PDF
|
||||
for optimal compatibility with your device's native PDF reader.
|
||||
|
||||
The absolute highest quality source files are print quality DRM-free PDFs from Kodansha/[Humble Bundle](https://humblebundleinc.sjv.io/xL6Zv1)/Fanatical,
|
||||
which can be directly converted by KCC.
|
||||
|
||||
Its main feature is various optional image processing steps to look good on eink screens,
|
||||
which have different requirements than normal LCD screens.
|
||||
@@ -94,7 +98,7 @@ The `c2e` and `c2p` versions are command line tools for power users.
|
||||
|
||||
On Windows 11, you may need to run in compatibility mode for an older Windows version.
|
||||
|
||||
On Mac, right click open to get past the security warning.
|
||||
On Mac, right click open to get past the security warning. macOS 12 Monterey or later is required, you can use https://dortania.github.io/OpenCore-Legacy-Patcher/ to get a newer macOS on unsupported hardware.
|
||||
|
||||
For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation
|
||||
|
||||
@@ -102,9 +106,11 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
|
||||
- Should I use Calibre?
|
||||
- No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output in Calibre will break the formatting.
|
||||
Viewing KCC output in Calibre will also not work properly.
|
||||
On 7th gen and later Kindles running firmware 5.16.3+, you can get cover thumbnails simply by USB dropping into documents folder.
|
||||
On 7th gen and later Kindles running firmware 5.15.1+, you can get cover thumbnails simply by USB dropping into documents folder.
|
||||
On 6th gen and older, you can get cover thumbnails by keeping Kindle plugged in during conversion.
|
||||
If you are careful to not modify the file however, you can still use Calibre, but direct USB dropping is reccomended.
|
||||
- What output format should I use?
|
||||
- MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo.
|
||||
- All options have additional information in tooltips if you hover over the option.
|
||||
- To get the converted book onto your Kindle/Kobo, just drag and drop the mobi/kepub into the documents folder on your Kindle/Kobo via USB
|
||||
- Right to left mode not working?
|
||||
@@ -117,11 +123,8 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
|
||||
- How to make AZW3 instead of MOBI?
|
||||
- The `.mobi` file generated by KCC is a dual filetype, it's both MOBI and AZW3. The file extension is `.mobi` for compatibility reasons.
|
||||
- [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)
|
||||
- Image too dark?
|
||||
- The default gamma correction of 1.8 makes the image darker, and is useful for faded/gray artwork/text. Disable by setting gamma = 1.0
|
||||
- [Better PDF support (Humble Bundle, Fanatical, etc)](https://github.com/ciromattia/kcc/issues/680)
|
||||
- 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.
|
||||
|
||||
@@ -253,11 +256,11 @@ OUTPUT SETTINGS:
|
||||
Output generated file to specified directory or file
|
||||
-t TITLE, --title TITLE
|
||||
Comic title [Default=filename or directory name]
|
||||
--comicinfotitle Write title from ComicInfo.xml
|
||||
--metadatatitle Write title from ComicInfo.xml or other embedded metadata
|
||||
-a AUTHOR, --author AUTHOR
|
||||
Author name [Default=KCC]
|
||||
-f FORMAT, --format FORMAT
|
||||
Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB) [Default=Auto]
|
||||
Output format (Available options: Auto, MOBI, EPUB, CBZ, PDF, KFX, MOBI+EPUB) [Default=Auto]
|
||||
--nokepub If format is EPUB, output file with '.epub' extension rather than '.kepub.epub'
|
||||
-b BATCHSPLIT, --batchsplit BATCHSPLIT
|
||||
Split output into multiple files. 0: Don't split 1: Automatic mode 2: Consider every subdirectory as separate volume [Default=0]
|
||||
@@ -403,7 +406,7 @@ Older links (dead):
|
||||
|
||||
## PRIVACY
|
||||
**KCC** is initiating internet connections in two cases:
|
||||
* During startup - Version check.
|
||||
* During startup - Version check and announcement check.
|
||||
* When error occurs - Automatic reporting on Windows and macOS.
|
||||
|
||||
## KNOWN ISSUES
|
||||
@@ -412,3 +415,6 @@ Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues).
|
||||
## COPYRIGHT
|
||||
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.
|
||||
|
||||
## Verification
|
||||
Impact-Site-Verification: ffe48fc7-4f0c-40fd-bd2e-59f4d7205180
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
<file>../icons/convert.png</file>
|
||||
<file>../icons/document_new.png</file>
|
||||
<file>../icons/folder_new.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="Brand">
|
||||
<file>../icons/kofi_symbol.png</file>
|
||||
<file>../icons/Humble_H-Red.png</file>
|
||||
<file>../icons/Bindle_Red.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
16
gui/KCC.ui
16
gui/KCC.ui
@@ -24,6 +24,12 @@
|
||||
</property>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QListWidget" name="jobList">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>150</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
@@ -86,7 +92,7 @@
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="KCC.qrc">
|
||||
<normaloff>:/Other/icons/kofi_symbol.png</normaloff>:/Other/icons/kofi_symbol.png</iconset>
|
||||
<normaloff>:/Brand/icons/kofi_symbol.png</normaloff>:/Other/icons/kofi_symbol.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
@@ -476,7 +482,7 @@
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="borderBox">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html></string>
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be untouched.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>W/B margins</string>
|
||||
@@ -585,12 +591,12 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QCheckBox" name="comicinfoTitleBox">
|
||||
<widget class="QCheckBox" name="metadataTitleBox">
|
||||
<property name="toolTip">
|
||||
<string>Write Title from ComicInfo.xml</string>
|
||||
<string><html><head/><body><p>Write Title from ComicInfo.xml or other embedded metadata.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>ComicInfo Title</string>
|
||||
<string>Metadata Title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
BIN
icons/Bindle_Red.png
Normal file
BIN
icons/Bindle_Red.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
BIN
icons/Humble_H-Red.png
Normal file
BIN
icons/Humble_H-Red.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -17,6 +17,7 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
from datetime import datetime, timezone
|
||||
import itertools
|
||||
from pathlib import Path
|
||||
from PySide6.QtCore import (QSize, QUrl, Qt, Signal, QIODeviceBase, QEvent, QThread, QSettings)
|
||||
@@ -124,7 +125,7 @@ class Icons:
|
||||
self.EPUBFormat = QIcon()
|
||||
self.EPUBFormat.addPixmap(QPixmap(":/Formats/icons/EPUB.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.KFXFormat = QIcon()
|
||||
self.KFXFormat.addPixmap(QPixmap(":/Formats/icons/KFX.png"), QIcon.Normal, QIcon.Off)
|
||||
self.KFXFormat.addPixmap(QPixmap(":/Formats/icons/KFX.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
|
||||
self.info = QIcon()
|
||||
self.info.addPixmap(QPixmap(":/Status/icons/info.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
@@ -136,6 +137,15 @@ class Icons:
|
||||
self.programIcon = QIcon()
|
||||
self.programIcon.addPixmap(QPixmap(":/Icon/icons/comic2ebook.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
|
||||
self.kofi = QIcon()
|
||||
self.kofi.addPixmap(QPixmap(":/Brand/icons/kofi_symbol.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
|
||||
self.humble = QIcon()
|
||||
self.humble.addPixmap(QPixmap(":/Brand/icons/Humble_H-Red.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
|
||||
self.bindle = QIcon()
|
||||
self.bindle.addPixmap(QPixmap(":/Brand/icons/Bindle_Red.png"), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
|
||||
|
||||
class VersionThread(QThread):
|
||||
def __init__(self):
|
||||
@@ -150,19 +160,50 @@ class VersionThread(QThread):
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
json_parser = requests.get("https://api.github.com/repos/ciromattia/kcc/releases/latest").json()
|
||||
# unauthenticated API requests limit is 60 req/hour
|
||||
if getattr(sys, 'frozen', False):
|
||||
json_parser = requests.get("https://api.github.com/repos/ciromattia/kcc/releases/latest").json()
|
||||
|
||||
html_url = json_parser["html_url"]
|
||||
latest_version = json_parser["tag_name"]
|
||||
latest_version = re.sub(r'^v', "", latest_version)
|
||||
html_url = json_parser["html_url"]
|
||||
latest_version = json_parser["tag_name"]
|
||||
latest_version = re.sub(r'^v', "", latest_version)
|
||||
|
||||
if ("b" not in __version__ and Version(latest_version) > Version(__version__)) \
|
||||
or ("b" in __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)
|
||||
if ("b" not in __version__ and Version(latest_version) > Version(__version__)) \
|
||||
or ("b" in __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:
|
||||
return
|
||||
pass
|
||||
|
||||
try:
|
||||
announcements = requests.get('https://api.github.com/repos/axu2/kcc-messages/contents/links.json',
|
||||
headers={
|
||||
'Accept': 'application/vnd.github.raw+json',
|
||||
'X-GitHub-Api-Version': '2022-11-28'}).json()
|
||||
for category, payloads in announcements.items():
|
||||
for payload in payloads:
|
||||
expiration = datetime.fromisoformat(payload['expiration'])
|
||||
if expiration < datetime.now(timezone.utc):
|
||||
continue
|
||||
delta = expiration - datetime.now(timezone.utc)
|
||||
time_left = f"{delta.days} day(s) left"
|
||||
icon = 'info'
|
||||
if category == 'humbleBundles':
|
||||
icon = 'bindle'
|
||||
if category == 'kofi':
|
||||
icon = 'kofi'
|
||||
message = f"<b>{payload.get('name')}</b>"
|
||||
if payload.get('link'):
|
||||
message = '<a href="{}"><b>{}</b></a>'.format(payload.get('link'), payload.get('name'))
|
||||
if payload.get('showDeadline'):
|
||||
message += f': {time_left}'
|
||||
if category == 'humbleBundles':
|
||||
message += ' [referral]'
|
||||
MW.addMessage.emit(message, icon , False)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def setAnswer(self, dialoganswer):
|
||||
self.answer = dialoganswer
|
||||
@@ -249,11 +290,23 @@ class WorkerThread(QThread):
|
||||
options.gamma = float(GUI.gammaValue)
|
||||
if GUI.autoLevelBox.isChecked():
|
||||
options.autolevel = True
|
||||
options.cropping = GUI.croppingBox.checkState().value
|
||||
if GUI.croppingBox.isChecked():
|
||||
if GUI.croppingBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||
options.cropping = 1
|
||||
else:
|
||||
options.cropping = 2
|
||||
else:
|
||||
options.cropping = 0
|
||||
if GUI.croppingBox.checkState() != Qt.CheckState.Unchecked:
|
||||
options.croppingp = float(GUI.croppingPowerValue)
|
||||
options.preservemargin = GUI.preserveMarginBox.value()
|
||||
options.interpanelcrop = GUI.interPanelCropBox.checkState().value
|
||||
if GUI.interPanelCropBox.isChecked():
|
||||
if GUI.interPanelCropBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||
options.interpanelcrop = 1
|
||||
else:
|
||||
options.interpanelcrop = 2
|
||||
else:
|
||||
options.interpanelcrop = 0
|
||||
if GUI.borderBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||
options.white_borders = True
|
||||
elif GUI.borderBox.checkState() == Qt.CheckState.Checked:
|
||||
@@ -268,8 +321,8 @@ class WorkerThread(QThread):
|
||||
options.maximizestrips = True
|
||||
if GUI.disableProcessingBox.isChecked():
|
||||
options.noprocessing = True
|
||||
if GUI.comicinfoTitleBox.isChecked():
|
||||
options.comicinfotitle = True
|
||||
if GUI.metadataTitleBox.isChecked():
|
||||
options.metadatatitle = True
|
||||
if GUI.deleteBox.isChecked():
|
||||
options.delete = True
|
||||
if GUI.spreadShiftBox.isChecked():
|
||||
@@ -324,6 +377,9 @@ class WorkerThread(QThread):
|
||||
if gui_current_format == 'CBZ':
|
||||
MW.addMessage.emit('Creating CBZ files', 'info', False)
|
||||
GUI.progress.content = 'Creating CBZ files'
|
||||
elif gui_current_format == 'PDF':
|
||||
MW.addMessage.emit('Creating PDF files', 'info', False)
|
||||
GUI.progress.content = 'Creating PDF files'
|
||||
else:
|
||||
MW.addMessage.emit('Creating EPUB files', 'info', False)
|
||||
GUI.progress.content = 'Creating EPUB files'
|
||||
@@ -368,6 +424,8 @@ class WorkerThread(QThread):
|
||||
GUI.progress.content = ''
|
||||
if gui_current_format == 'CBZ':
|
||||
MW.addMessage.emit('Creating CBZ files... <b>Done!</b>', 'info', True)
|
||||
elif gui_current_format == 'PDF':
|
||||
MW.addMessage.emit('Creating PDF files... <b>Done!</b>', 'info', True)
|
||||
else:
|
||||
MW.addMessage.emit('Creating EPUB files... <b>Done!</b>', 'info', True)
|
||||
if 'MOBI' in gui_current_format:
|
||||
@@ -464,7 +522,7 @@ class WorkerThread(QThread):
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
elif os.path.isdir(path):
|
||||
rmtree(path)
|
||||
rmtree(path, True)
|
||||
GUI.progress.content = ''
|
||||
GUI.progress.stop()
|
||||
MW.hideProgressBar.emit()
|
||||
@@ -861,35 +919,35 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.settings.setValue('currentFormat', GUI.formatBox.currentIndex())
|
||||
self.settings.setValue('startNumber', self.startNumber + 1)
|
||||
self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height()))
|
||||
self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState().value,
|
||||
'rotateBox': GUI.rotateBox.checkState().value,
|
||||
'qualityBox': GUI.qualityBox.checkState().value,
|
||||
'gammaBox': GUI.gammaBox.checkState().value,
|
||||
'autoLevelBox': GUI.autoLevelBox.checkState().value,
|
||||
'croppingBox': GUI.croppingBox.checkState().value,
|
||||
self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(),
|
||||
'rotateBox': GUI.rotateBox.checkState(),
|
||||
'qualityBox': GUI.qualityBox.checkState(),
|
||||
'gammaBox': GUI.gammaBox.checkState(),
|
||||
'autoLevelBox': GUI.autoLevelBox.checkState(),
|
||||
'croppingBox': GUI.croppingBox.checkState(),
|
||||
'croppingPowerSlider': float(self.croppingPowerValue) * 100,
|
||||
'preserveMarginBox': self.preserveMarginBox.value(),
|
||||
'interPanelCropBox': GUI.interPanelCropBox.checkState().value,
|
||||
'upscaleBox': GUI.upscaleBox.checkState().value,
|
||||
'borderBox': GUI.borderBox.checkState().value,
|
||||
'webtoonBox': GUI.webtoonBox.checkState().value,
|
||||
'outputSplit': GUI.outputSplit.checkState().value,
|
||||
'colorBox': GUI.colorBox.checkState().value,
|
||||
'eraseRainbowBox': GUI.eraseRainbowBox.checkState().value,
|
||||
'disableProcessingBox': GUI.disableProcessingBox.checkState().value,
|
||||
'comicinfoTitleBox': GUI.comicinfoTitleBox.checkState().value,
|
||||
'mozJpegBox': GUI.mozJpegBox.checkState().value,
|
||||
'interPanelCropBox': GUI.interPanelCropBox.checkState(),
|
||||
'upscaleBox': GUI.upscaleBox.checkState(),
|
||||
'borderBox': GUI.borderBox.checkState(),
|
||||
'webtoonBox': GUI.webtoonBox.checkState(),
|
||||
'outputSplit': GUI.outputSplit.checkState(),
|
||||
'colorBox': GUI.colorBox.checkState(),
|
||||
'eraseRainbowBox': GUI.eraseRainbowBox.checkState(),
|
||||
'disableProcessingBox': GUI.disableProcessingBox.checkState(),
|
||||
'metadataTitleBox': GUI.metadataTitleBox.checkState(),
|
||||
'mozJpegBox': GUI.mozJpegBox.checkState(),
|
||||
'widthBox': GUI.widthBox.value(),
|
||||
'heightBox': GUI.heightBox.value(),
|
||||
'deleteBox': GUI.deleteBox.checkState().value,
|
||||
'spreadShiftBox': GUI.spreadShiftBox.checkState().value,
|
||||
'fileFusionBox': GUI.fileFusionBox.checkState().value,
|
||||
'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState().value,
|
||||
'noRotateBox': GUI.noRotateBox.checkState().value,
|
||||
'rotateFirstBox': GUI.rotateFirstBox.checkState().value,
|
||||
'maximizeStrips': GUI.maximizeStrips.checkState().value,
|
||||
'deleteBox': GUI.deleteBox.checkState(),
|
||||
'spreadShiftBox': GUI.spreadShiftBox.checkState(),
|
||||
'fileFusionBox': GUI.fileFusionBox.checkState(),
|
||||
'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState(),
|
||||
'noRotateBox': GUI.noRotateBox.checkState(),
|
||||
'rotateFirstBox': GUI.rotateFirstBox.checkState(),
|
||||
'maximizeStrips': GUI.maximizeStrips.checkState(),
|
||||
'gammaSlider': float(self.gammaValue) * 100,
|
||||
'chunkSizeCheckBox': GUI.chunkSizeCheckBox.checkState().value,
|
||||
'chunkSizeCheckBox': GUI.chunkSizeCheckBox.checkState(),
|
||||
'chunkSizeBox': GUI.chunkSizeBox.value()})
|
||||
self.settings.sync()
|
||||
self.tray.hide()
|
||||
@@ -964,7 +1022,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.setupUi(MW)
|
||||
self.editor = KCCGUI_MetaEditor()
|
||||
self.icons = Icons()
|
||||
self.settings = QSettings('ciromattia', 'kcc')
|
||||
self.settings = QSettings('ciromattia', 'kcc9')
|
||||
self.settingsVersion = self.settings.value('settingsVersion', '', type=str)
|
||||
self.lastPath = self.settings.value('lastPath', '', type=str)
|
||||
self.defaultOutputFolder = str(self.settings.value('defaultOutputFolder', '', type=str))
|
||||
@@ -974,7 +1032,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.currentFormat = self.settings.value('currentFormat', 0, type=int)
|
||||
self.startNumber = self.settings.value('startNumber', 0, type=int)
|
||||
self.windowSize = self.settings.value('windowSize', '0x0', type=str)
|
||||
self.options = self.settings.value('options', {'gammaSlider': 0, 'croppingBox': 2, 'croppingPowerSlider': 100})
|
||||
default_options = {'gammaSlider': 0, 'croppingBox': 2, 'croppingPowerSlider': 100}
|
||||
try:
|
||||
self.options = self.settings.value('options', default_options)
|
||||
except Exception:
|
||||
self.options = default_options
|
||||
self.worker = WorkerThread()
|
||||
self.versionCheck = VersionThread()
|
||||
self.progress = ProgressThread()
|
||||
@@ -1011,6 +1073,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
"MOBI/AZW3": {'icon': 'MOBI', 'format': 'MOBI'},
|
||||
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
|
||||
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
|
||||
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
|
||||
"KFX (does not work)": {'icon': 'KFX', 'format': 'KFX'},
|
||||
"MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'},
|
||||
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'},
|
||||
@@ -1092,11 +1155,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'Label': 'KoS'},
|
||||
"Kobo Elipsa": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, 'DefaultUpscale': True, 'ForceColor': False,
|
||||
'Label': 'KoE'},
|
||||
"reMarkable 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, 'DefaultUpscale': True, 'ForceColor': False,
|
||||
"reMarkable 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 3, 'DefaultUpscale': True, 'ForceColor': False,
|
||||
'Label': 'Rmk1'},
|
||||
"reMarkable 2": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, 'DefaultUpscale': True, 'ForceColor': False,
|
||||
"reMarkable 2": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 3, 'DefaultUpscale': True, 'ForceColor': False,
|
||||
'Label': 'Rmk2'},
|
||||
"reMarkable Paper Pro": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, 'DefaultUpscale': True, 'ForceColor': True,
|
||||
"reMarkable Paper Pro": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 3, 'DefaultUpscale': True, 'ForceColor': True,
|
||||
'Label': 'RmkPP'},
|
||||
"Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1, 'DefaultUpscale': False, 'ForceColor': False,
|
||||
'Label': 'OTHER'},
|
||||
@@ -1163,7 +1226,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
statusBarLabel.setOpenExternalLinks(True)
|
||||
GUI.statusBar.addPermanentWidget(statusBarLabel, 1)
|
||||
|
||||
self.addMessage('<b>Welcome!</b>', 'info')
|
||||
self.addMessage('<b>Tip:</b> Hover mouse over options to see additional information in tooltips.', 'info')
|
||||
self.addMessage('<b>Tip:</b> You can drag and drop image folders or comic files/archives into this window to convert.', 'info')
|
||||
if self.startNumber < 5:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,7 @@ class Ui_mainWindow(object):
|
||||
self.gridLayout.setContentsMargins(-1, -1, -1, 5)
|
||||
self.jobList = QListWidget(self.centralWidget)
|
||||
self.jobList.setObjectName(u"jobList")
|
||||
self.jobList.setMinimumSize(QSize(0, 150))
|
||||
self.jobList.setStyleSheet(u"")
|
||||
self.jobList.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
||||
self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
@@ -62,7 +63,7 @@ class Ui_mainWindow(object):
|
||||
self.kofiButton.setObjectName(u"kofiButton")
|
||||
self.kofiButton.setMinimumSize(QSize(0, 30))
|
||||
icon2 = QIcon()
|
||||
icon2.addFile(u":/Other/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
icon2.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||
self.kofiButton.setIcon(icon2)
|
||||
self.kofiButton.setIconSize(QSize(19, 16))
|
||||
|
||||
@@ -316,10 +317,10 @@ class Ui_mainWindow(object):
|
||||
|
||||
self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
|
||||
|
||||
self.comicinfoTitleBox = QCheckBox(self.optionWidget)
|
||||
self.comicinfoTitleBox.setObjectName(u"comicinfoTitleBox")
|
||||
self.metadataTitleBox = QCheckBox(self.optionWidget)
|
||||
self.metadataTitleBox.setObjectName(u"metadataTitleBox")
|
||||
|
||||
self.gridLayout_2.addWidget(self.comicinfoTitleBox, 7, 0, 1, 1)
|
||||
self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1)
|
||||
|
||||
self.qualityBox = QCheckBox(self.optionWidget)
|
||||
self.qualityBox.setObjectName(u"qualityBox")
|
||||
@@ -546,7 +547,7 @@ class Ui_mainWindow(object):
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left mode", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>", None))
|
||||
self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be untouched.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
@@ -582,9 +583,9 @@ class Ui_mainWindow(object):
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.comicinfoTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"Write Title from ComicInfo.xml", None))
|
||||
self.metadataTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Write Title from ComicInfo.xml or other embedded metadata.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
self.comicinfoTitleBox.setText(QCoreApplication.translate("mainWindow", u"ComicInfo Title", None))
|
||||
self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None))
|
||||
#if QT_CONFIG(tooltip)
|
||||
self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>", None))
|
||||
#endif // QT_CONFIG(tooltip)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = '9.0.0'
|
||||
__version__ = '9.1.0'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -44,7 +44,7 @@ from html import escape as hescape
|
||||
import pymupdf
|
||||
import numpy as np
|
||||
|
||||
from .shared import getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run
|
||||
from .shared import getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean
|
||||
from .comicarchive import SEVENZIP, available_archive_tools
|
||||
from . import comic2panel
|
||||
from . import image
|
||||
@@ -126,9 +126,10 @@ def buildHTML(path, imgfile, imgfilepath, imgfile2=None):
|
||||
"</head>\n",
|
||||
"<body style=\"" + additionalStyle + "\">\n",
|
||||
"<div style=\"text-align:center;top:" + getTopMargin(deviceres, imgsizeframe) + "%;\">\n",
|
||||
# this display none div fixes formatting issues with virtual panel mode, for some reason
|
||||
'<div style="display:none;">.</div>\n',
|
||||
])
|
||||
if options.iskindle:
|
||||
# this display none div fixes formatting issues with virtual panel mode, for some reason
|
||||
f.write('<div style="display:none;">.</div>\n')
|
||||
f.write(f'<img width="{imgsize[0]}" height="{imgsize[1]}" src="{"../" * backref}Images/{postfix}{imgfile}"/>\n')
|
||||
if imgfile2:
|
||||
f.write(f'<img width="{imgsize2[0]}" height="{imgsize2[1]}" src="{"../" * backref}Images/{postfix}{imgfile2}"/>\n')
|
||||
@@ -277,7 +278,7 @@ def buildNAV(dstdir, title, chapters, chapternames):
|
||||
f.close()
|
||||
|
||||
|
||||
def buildOPF(dstdir, title, filelist, cover=None):
|
||||
def buildOPF(dstdir, title, filelist, originalpath, cover=None):
|
||||
opffile = os.path.join(dstdir, 'OEBPS', 'content.opf')
|
||||
deviceres = options.profileData[1]
|
||||
if options.righttoleft:
|
||||
@@ -365,6 +366,11 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
||||
else:
|
||||
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
|
||||
pageside = "left"
|
||||
if originalpath.lower().endswith('.pdf'):
|
||||
if pageside == "right":
|
||||
pageside = "left"
|
||||
else:
|
||||
pageside = "right"
|
||||
if options.spreadshift:
|
||||
if pageside == "right":
|
||||
pageside = "left"
|
||||
@@ -439,7 +445,7 @@ def buildOPF(dstdir, title, filelist, cover=None):
|
||||
"</container>"])
|
||||
f.close()
|
||||
|
||||
def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, len_tomes=0):
|
||||
def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, originalpath, len_tomes=0):
|
||||
filelist = []
|
||||
chapterlist = []
|
||||
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
||||
@@ -527,6 +533,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, len
|
||||
f.close()
|
||||
build_html_start = perf_counter()
|
||||
cover.save_to_epub(os.path.join(path, 'OEBPS', 'Images', 'cover.jpg'), tomenumber, len_tomes)
|
||||
dot_clean(path)
|
||||
options.covers.append((cover, options.uuid))
|
||||
for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||
chapter = False
|
||||
@@ -578,7 +585,36 @@ def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, len
|
||||
chapternames[filename] = aChapter[1]
|
||||
buildNCX(path, options.title, chapterlist, chapternames)
|
||||
buildNAV(path, options.title, chapterlist, chapternames)
|
||||
buildOPF(path, options.title, filelist, cover)
|
||||
buildOPF(path, options.title, filelist, originalpath, cover)
|
||||
|
||||
|
||||
def buildPDF(path, title, cover=None, output_file=None):
|
||||
"""
|
||||
Build a PDF file from processed comic images.
|
||||
Images are combined into a single PDF optimized for e-readers.
|
||||
"""
|
||||
start = perf_counter()
|
||||
# open empty PDF
|
||||
with pymupdf.open() as doc:
|
||||
doc.set_metadata({'title': title, 'author': options.authors[0]})
|
||||
# Stream images to PDF
|
||||
for root, dirs, files in os.walk(os.path.join(path, "OEBPS", "Images")):
|
||||
files.sort(key=OS_SORT_KEY)
|
||||
dirs.sort(key=OS_SORT_KEY)
|
||||
for file in files:
|
||||
w, h = Image.open(os.path.join(root, file)).size
|
||||
page = doc.new_page(width=w, height=h)
|
||||
page.insert_image(page.rect, filename=os.path.join(root, file))
|
||||
|
||||
# determine output filename if not provided
|
||||
if output_file is None:
|
||||
output_file = getOutputFilename(path, None, '.pdf', '')
|
||||
|
||||
# Save with optimizations for smaller file size
|
||||
doc.save(output_file, deflate=True, garbage=4, clean=True)
|
||||
end = perf_counter()
|
||||
print(f"MuPDF output: {end-start} sec")
|
||||
return output_file
|
||||
|
||||
|
||||
def imgDirectoryProcessing(path):
|
||||
@@ -658,7 +694,8 @@ def imgFileProcessing(work):
|
||||
pass
|
||||
elif opt.forcepng:
|
||||
img.convertToGrayscale()
|
||||
img.quantizeImage()
|
||||
if opt.format != 'PDF':
|
||||
img.quantizeImage()
|
||||
else:
|
||||
img.convertToGrayscale()
|
||||
output.append(img.saveToDir())
|
||||
@@ -753,16 +790,16 @@ def extract_page(vector):
|
||||
width, height = int(page.rect.width), int(page.rect.height)
|
||||
blank_page = Image.new("RGB", (width, height), "white")
|
||||
blank_page.save(output_path)
|
||||
xref = image_list[0][0]
|
||||
d = doc.extract_image(xref)
|
||||
if d['cs-name'] == 'DeviceCMYK':
|
||||
pix = pymupdf.Pixmap(doc, xref)
|
||||
pix = pymupdf.Pixmap(pymupdf.csRGB, pix)
|
||||
pix.save(output_path)
|
||||
|
||||
else:
|
||||
with open(Path(output_path).with_suffix('.' + d['ext']), "wb") as imgout:
|
||||
imgout.write(d["image"])
|
||||
xref = image_list[0][0]
|
||||
d = doc.extract_image(xref)
|
||||
if d['cs-name'] == 'DeviceCMYK':
|
||||
pix = pymupdf.Pixmap(doc, xref)
|
||||
pix = pymupdf.Pixmap(pymupdf.csRGB, pix)
|
||||
pix.save(output_path)
|
||||
else:
|
||||
with open(Path(output_path).with_suffix('.' + d['ext']), "wb") as imgout:
|
||||
imgout.write(d["image"])
|
||||
print("Processed page numbers %i through %i" % (seg_from, seg_to - 1))
|
||||
|
||||
|
||||
@@ -778,6 +815,11 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height):
|
||||
if len(page.get_images()) > 1:
|
||||
render = True
|
||||
break
|
||||
if len(page.get_images()) == 1:
|
||||
image = page.get_images()[0]
|
||||
if not image[5] or image[8] == 'CCITTFaxDecode':
|
||||
render = True
|
||||
break
|
||||
|
||||
cpu = cpu_count()
|
||||
|
||||
@@ -890,7 +932,7 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
||||
return filename
|
||||
|
||||
|
||||
def getComicInfo(path, originalpath):
|
||||
def getMetadata(path, originalpath):
|
||||
xmlPath = os.path.join(path, 'ComicInfo.xml')
|
||||
options.comicinfo_chapters = []
|
||||
options.summary = ''
|
||||
@@ -909,13 +951,14 @@ def getComicInfo(path, originalpath):
|
||||
else:
|
||||
defaultAuthor = False
|
||||
options.authors = [options.author]
|
||||
|
||||
if os.path.exists(xmlPath):
|
||||
try:
|
||||
xml = metadata.MetadataParser(xmlPath)
|
||||
except Exception:
|
||||
os.remove(xmlPath)
|
||||
return
|
||||
if options.comicinfotitle:
|
||||
if options.metadatatitle:
|
||||
options.title = xml.data['Title']
|
||||
elif defaultTitle:
|
||||
if xml.data['Series']:
|
||||
@@ -941,6 +984,13 @@ def getComicInfo(path, originalpath):
|
||||
options.summary = xml.data['Summary']
|
||||
os.remove(xmlPath)
|
||||
|
||||
if originalpath.lower().endswith('.pdf'):
|
||||
with pymupdf.open(originalpath) as doc:
|
||||
if options.metadatatitle and doc.metadata['title']:
|
||||
options.title = doc.metadata['title']
|
||||
if defaultAuthor and doc.metadata['author']:
|
||||
options.authors = [doc.metadata['author']]
|
||||
|
||||
|
||||
def getDirectorySize(start_path='.'):
|
||||
total_size = 0
|
||||
@@ -1040,19 +1090,12 @@ def sanitizePermissions(filetree):
|
||||
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):
|
||||
level = -1
|
||||
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||
for f in files:
|
||||
# Windows MAX_LENGTH = 260 plus some buffer
|
||||
if len(os.path.join(root, f)) > 180:
|
||||
# Windows MAX_LEN = 260 plus some buffer
|
||||
if os.name == 'nt' and len(os.path.join(root, f)) > 180:
|
||||
flattenTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
level = 1
|
||||
break
|
||||
@@ -1143,6 +1186,7 @@ def detectSuboptimalProcessing(tmppath, orgpath):
|
||||
try:
|
||||
img = Image.open(path)
|
||||
imageNumber += 1
|
||||
# count images smaller than device resolution
|
||||
if options.profileData[1][0] > img.size[0] and options.profileData[1][1] > img.size[1]:
|
||||
imageSmaller += 1
|
||||
except Exception as err:
|
||||
@@ -1190,7 +1234,6 @@ def slugify(value, is_natural_sorted):
|
||||
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
||||
return value
|
||||
|
||||
|
||||
def makeZIP(zipfilename, basedir, isepub=False):
|
||||
start = perf_counter()
|
||||
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
||||
@@ -1215,7 +1258,6 @@ def makeZIP(zipfilename, basedir, isepub=False):
|
||||
print(f"makeZIP time: {end - start} seconds")
|
||||
return zipfilename
|
||||
|
||||
|
||||
def makeParser():
|
||||
psr = ArgumentParser(prog="kcc-c2e", usage="kcc-c2e [options] [input]", add_help=False)
|
||||
|
||||
@@ -1248,12 +1290,12 @@ def makeParser():
|
||||
help="Output generated file to specified directory or file")
|
||||
output_options.add_argument("-t", "--title", action="store", dest="title", default="defaulttitle",
|
||||
help="Comic title [Default=filename or directory name]")
|
||||
output_options.add_argument("--comicinfotitle", action="store_true", dest="comicinfotitle", default=False,
|
||||
help="Write Title from ComicInfo.xml")
|
||||
output_options.add_argument("--metadatatitle", action="store_true", dest="metadatatitle", default=False,
|
||||
help="Write Title from ComicInfo.xml or other embedded metadata")
|
||||
output_options.add_argument("-a", "--author", action="store", dest="author", default="defaultauthor",
|
||||
help="Author name [Default=KCC]")
|
||||
output_options.add_argument("-f", "--format", action="store", dest="format", default="Auto",
|
||||
help="Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB) "
|
||||
help="Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB, PDF) "
|
||||
"[Default=Auto]")
|
||||
output_options.add_argument("--nokepub", action="store_true", dest="noKepub", default=False,
|
||||
help="If format is EPUB, output file with '.epub' extension rather than '.kepub.epub'")
|
||||
@@ -1343,6 +1385,8 @@ def checkOptions(options):
|
||||
options.format = 'CBZ'
|
||||
elif options.profile in image.ProfileData.ProfilesKindle.keys():
|
||||
options.format = 'MOBI'
|
||||
elif options.profile in image.ProfileData.ProfilesRemarkable.keys():
|
||||
options.format = 'PDF'
|
||||
else:
|
||||
options.format = 'EPUB'
|
||||
if options.profile in image.ProfileData.ProfilesKindle.keys():
|
||||
@@ -1479,7 +1523,7 @@ def makeBook(source, qtgui=None):
|
||||
print("Preparing source images...")
|
||||
path = getWorkFolder(source)
|
||||
print("Checking images...")
|
||||
getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
|
||||
getMetadata(os.path.join(path, "OEBPS", "Images"), source)
|
||||
removeNonImages(os.path.join(path, "OEBPS", "Images"))
|
||||
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
|
||||
chapterNames, cover_path = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
@@ -1497,7 +1541,7 @@ def makeBook(source, qtgui=None):
|
||||
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"))
|
||||
if GUI:
|
||||
GUI.progressBarTick.emit('1')
|
||||
if options.batchsplit > 0:
|
||||
if options.batchsplit > 0 or options.targetsize:
|
||||
tomes = chunk_directory(path)
|
||||
else:
|
||||
tomes = [path]
|
||||
@@ -1506,6 +1550,8 @@ def makeBook(source, qtgui=None):
|
||||
if GUI:
|
||||
if options.format == 'CBZ':
|
||||
GUI.progressBarTick.emit('Compressing CBZ files')
|
||||
elif options.format == 'PDF':
|
||||
GUI.progressBarTick.emit('Creating PDF files')
|
||||
else:
|
||||
GUI.progressBarTick.emit('Compressing EPUB files')
|
||||
GUI.progressBarTick.emit(str(len(tomes) + 1))
|
||||
@@ -1527,21 +1573,31 @@ def makeBook(source, qtgui=None):
|
||||
else:
|
||||
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
|
||||
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"))
|
||||
elif options.format == 'PDF':
|
||||
print("Creating PDF file with PyMuPDF...")
|
||||
# determine output filename based on source and tome count
|
||||
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
|
||||
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
|
||||
# use optimized buildPDF logic with streaming and compression
|
||||
output_pdf = buildPDF(tome, options.title, None, output_file)
|
||||
filepath.append(output_pdf)
|
||||
else:
|
||||
print("Creating EPUB file...")
|
||||
if len(tomes) > 1:
|
||||
buildEPUB(tome, chapterNames, tomeNumber, True, cover, len(tomes))
|
||||
buildEPUB(tome, chapterNames, tomeNumber, True, cover, source, len(tomes))
|
||||
filepath.append(getOutputFilename(source, options.output, '.epub', ' ' + str(tomeNumber)))
|
||||
else:
|
||||
buildEPUB(tome, chapterNames, tomeNumber, False, cover)
|
||||
buildEPUB(tome, chapterNames, tomeNumber, False, cover, source)
|
||||
filepath.append(getOutputFilename(source, options.output, '.epub', ''))
|
||||
makeZIP(tome + '_comic', tome, True)
|
||||
copyfile(tome + '_comic.zip', filepath[-1])
|
||||
try:
|
||||
os.remove(tome + '_comic.zip')
|
||||
except FileNotFoundError:
|
||||
# newly temporary created file is not found. It might have been already deleted
|
||||
pass
|
||||
# Copy files to final destination (PDF files are already saved directly)
|
||||
if options.format != 'PDF':
|
||||
copyfile(tome + '_comic.zip', filepath[-1])
|
||||
try:
|
||||
os.remove(tome + '_comic.zip')
|
||||
except FileNotFoundError:
|
||||
# newly temporary created file is not found. It might have been already deleted
|
||||
pass
|
||||
rmtree(tome, True)
|
||||
if GUI:
|
||||
GUI.progressBarTick.emit('tick')
|
||||
@@ -1572,10 +1628,15 @@ def makeBook(source, qtgui=None):
|
||||
if os.path.isfile(source):
|
||||
os.remove(source)
|
||||
elif os.path.isdir(source):
|
||||
rmtree(source)
|
||||
rmtree(source, True)
|
||||
|
||||
end = perf_counter()
|
||||
print(f"makeBook: {end - start} seconds")
|
||||
# Clean up temporary workspace
|
||||
try:
|
||||
rmtree(path, True)
|
||||
except Exception:
|
||||
pass
|
||||
return filepath
|
||||
|
||||
|
||||
@@ -1655,3 +1716,4 @@ def makeMOBI(work, qtgui=None):
|
||||
makeMOBIWorkerPool.close()
|
||||
makeMOBIWorkerPool.join()
|
||||
return makeMOBIWorkerOutput
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from argparse import ArgumentParser
|
||||
from shutil import rmtree, copytree, move
|
||||
from multiprocessing import Pool
|
||||
from PIL import Image, ImageChops, ImageOps, ImageDraw
|
||||
from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace
|
||||
from .shared import dot_clean, getImageFileName, walkLevel, walkSort, sanitizeTrace
|
||||
|
||||
|
||||
def mergeDirectoryTick(output):
|
||||
@@ -44,6 +44,7 @@ def mergeDirectory(work):
|
||||
imagesValid = []
|
||||
sizes = []
|
||||
targetHeight = 0
|
||||
dot_clean(directory)
|
||||
for root, _, files in walkLevel(directory, 0):
|
||||
for name in files:
|
||||
if getImageFileName(name) is not None:
|
||||
@@ -253,6 +254,7 @@ def main(argv=None, qtgui=None):
|
||||
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
|
||||
mergeWorkerOutput[0][1])
|
||||
print("Splitting images...")
|
||||
dot_clean(targetDir)
|
||||
for root, _, files in os.walk(targetDir, False):
|
||||
for name in files:
|
||||
if getImageFileName(name) is not None:
|
||||
@@ -277,7 +279,7 @@ def main(argv=None, qtgui=None):
|
||||
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
|
||||
splitWorkerOutput[0][1])
|
||||
if args.inPlace:
|
||||
rmtree(sourceDir)
|
||||
rmtree(sourceDir, True)
|
||||
move(targetDir, sourceDir)
|
||||
else:
|
||||
rmtree(targetDir, True)
|
||||
|
||||
@@ -383,7 +383,7 @@ class ComicPage:
|
||||
|
||||
def optimizeForDisplay(self, eraserainbow, is_color):
|
||||
# Erase rainbow artifacts for grayscale and color images by removing spectral frequencies that cause Moire interference with color filter array
|
||||
if eraserainbow:
|
||||
if eraserainbow and all(dim > 1 for dim in self.image.size):
|
||||
self.image = erase_rainbow_artifacts(self.image, is_color)
|
||||
|
||||
def resizeImage(self):
|
||||
@@ -397,7 +397,7 @@ class ComicPage:
|
||||
else: # if image bigger than device resolution or smaller with upscaling
|
||||
if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
||||
self.image = ImageOps.fit(self.image, self.size, method=method)
|
||||
elif (self.opt.format == 'CBZ' or self.opt.kfx) and not self.opt.white_borders:
|
||||
elif (self.opt.format in ('CBZ', 'PDF') or self.opt.kfx) and not self.opt.white_borders:
|
||||
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
|
||||
else:
|
||||
self.image = ImageOps.contain(self.image, self.size, method=method)
|
||||
@@ -423,12 +423,20 @@ class ComicPage:
|
||||
bbox = get_bbox_crop_margin_page_number(self.image, power, self.fill)
|
||||
|
||||
if bbox:
|
||||
w, h = self.image.size
|
||||
left, upper, right, lower = bbox
|
||||
# don't crop more than 10% of image
|
||||
bbox = (min(0.1*w, left), min(0.1*h, upper), max(0.9*w, right), max(0.9*h, lower))
|
||||
self.maybeCrop(bbox, minimum)
|
||||
|
||||
def cropMargin(self, power, minimum):
|
||||
bbox = get_bbox_crop_margin(self.image, power, self.fill)
|
||||
|
||||
if bbox:
|
||||
w, h = self.image.size
|
||||
left, upper, right, lower = bbox
|
||||
# don't crop more than 10% of image
|
||||
bbox = (min(0.1*w, left), min(0.1*h, upper), max(0.9*w, right), max(0.9*h, lower))
|
||||
self.maybeCrop(bbox, minimum)
|
||||
|
||||
def cropInterPanelEmptySections(self, direction):
|
||||
@@ -463,7 +471,7 @@ class Cover:
|
||||
self.image = self.image.crop((w/6, 0, w/2 - w * 0.02, h))
|
||||
else:
|
||||
self.image = self.image.crop((w/2 + w * 0.02, 0, 5/6 * w, h))
|
||||
elif w / h > 1.3:
|
||||
elif w / h > 1.34:
|
||||
if self.options.righttoleft:
|
||||
self.image = self.image.crop((0, 0, w/2 - w * 0.03, h))
|
||||
else:
|
||||
@@ -487,9 +495,6 @@ class Cover:
|
||||
stroke_width=25
|
||||
)
|
||||
copy.save(target, "JPEG", optimize=1, quality=85)
|
||||
dot_cover = Path(target).with_stem('._' + Path(target).stem)
|
||||
if os.path.exists(dot_cover):
|
||||
os.remove(dot_cover)
|
||||
except IOError:
|
||||
raise RuntimeError('Failed to save cover.')
|
||||
|
||||
|
||||
@@ -123,4 +123,4 @@ class MetadataParser:
|
||||
cbx.addFile(tmpXML)
|
||||
except OSError as e:
|
||||
raise UserWarning(e)
|
||||
rmtree(workdir)
|
||||
rmtree(workdir, True)
|
||||
|
||||
@@ -52,6 +52,7 @@ def get_bbox_crop_margin_page_number(img, power=1, background_color='white'):
|
||||
'''
|
||||
threshold = threshold_from_power(power)
|
||||
bw_img = img.point(lambda p: 255 if p <= threshold else 0)
|
||||
ignore_pixels_near_edge(bw_img)
|
||||
bw_bbox = bw_img.getbbox()
|
||||
if not bw_bbox: # bbox cannot be found in case that the entire resulted image is black.
|
||||
return None
|
||||
@@ -141,9 +142,26 @@ def get_bbox_crop_margin(img, power=1, background_color='white'):
|
||||
'''
|
||||
threshold = threshold_from_power(power)
|
||||
bw_img = img.point(lambda p: 255 if p <= threshold else 0)
|
||||
|
||||
ignore_pixels_near_edge(bw_img)
|
||||
|
||||
return bw_img.getbbox()
|
||||
|
||||
def ignore_pixels_near_edge(bw_img):
|
||||
w, h = bw_img.size
|
||||
edge_bbox = [
|
||||
(0, 0, w, int(0.02 * h)),
|
||||
(0, int(0.98 * h), w, h),
|
||||
(0, 0, int(0.02 * w), h),
|
||||
(int(0.98 * w), 0, w, h)
|
||||
]
|
||||
for box in edge_bbox:
|
||||
edge = bw_img.crop(box)
|
||||
h = edge.histogram()
|
||||
imperfections = h[255] / (edge.height * edge.width)
|
||||
if imperfections > 0 and imperfections < .02:
|
||||
bw_img.paste(im=0, box=box)
|
||||
|
||||
|
||||
def box_intersect(box1, box2, max_dist):
|
||||
return not (box2[0]-max_dist[0] > box1[1]
|
||||
|
||||
@@ -45,6 +45,14 @@ class HTMLStripper(HTMLParser):
|
||||
pass
|
||||
|
||||
|
||||
def dot_clean(filetree):
|
||||
for root, _, files in os.walk(filetree, topdown=False):
|
||||
for name in files:
|
||||
if name.startswith('._') or name == '.DS_Store':
|
||||
if os.path.exists(os.path.join(root, name)):
|
||||
os.remove(os.path.join(root, name))
|
||||
|
||||
|
||||
def getImageFileName(imgfile):
|
||||
name, ext = os.path.splitext(imgfile)
|
||||
ext = ext.lower()
|
||||
@@ -90,10 +98,10 @@ def dependencyCheck(level):
|
||||
if level > 2:
|
||||
try:
|
||||
from PySide6.QtCore import qVersion as qtVersion
|
||||
if Version('6.5.1') > Version(qtVersion()):
|
||||
missing.append('PySide 6.5.1+')
|
||||
if Version('6.0.0') > Version(qtVersion()):
|
||||
missing.append('PySide 6.0.0')
|
||||
except ImportError:
|
||||
missing.append('PySide 6.5.1+')
|
||||
missing.append('PySide 6.0.0+')
|
||||
try:
|
||||
import raven
|
||||
except ImportError:
|
||||
@@ -116,16 +124,16 @@ def dependencyCheck(level):
|
||||
missing.append('python-slugify 1.2.1+')
|
||||
try:
|
||||
from PIL import __version__ as pillowVersion
|
||||
if Version('11.3.0') > Version(pillowVersion):
|
||||
missing.append('Pillow 11.3.0+')
|
||||
if Version('8.3.0') > Version(pillowVersion):
|
||||
missing.append('Pillow 8.3.0+')
|
||||
except ImportError:
|
||||
missing.append('Pillow 11.3.0+')
|
||||
missing.append('Pillow 8.3.0+')
|
||||
try:
|
||||
from pymupdf import __version__ as pymupdfVersion
|
||||
if Version('1.26.1') > Version(pymupdfVersion):
|
||||
missing.append('PyMuPDF 1.26.1+')
|
||||
if Version('1.16.1') > Version(pymupdfVersion):
|
||||
missing.append('PyMuPDF 1.16.1+')
|
||||
except ImportError:
|
||||
missing.append('PyMuPDF 1.26.1+')
|
||||
missing.append('PyMuPDF 1.16.1+')
|
||||
if len(missing) > 0:
|
||||
print('ERROR: ' + ', '.join(missing) + ' is not installed!')
|
||||
sys.exit(1)
|
||||
|
||||
12
requirements-osx-legacy.txt
Normal file
12
requirements-osx-legacy.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
PySide6==6.5.2
|
||||
Pillow>=11.3.0
|
||||
psutil>=5.9.5
|
||||
requests>=2.31.0
|
||||
python-slugify>=1.2.1
|
||||
raven>=6.0.0
|
||||
packaging>=23.2
|
||||
mozjpeg-lossless-optimization>=1.2.0
|
||||
natsort>=8.4.0
|
||||
distro>=1.8.0
|
||||
numpy<2
|
||||
PyMuPDF>=1.26.1
|
||||
12
requirements-win7.txt
Normal file
12
requirements-win7.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
PySide6==6.1.3
|
||||
Pillow>=9
|
||||
psutil>=5.9.5
|
||||
requests>=2.31.0
|
||||
python-slugify>=1.2.1
|
||||
raven>=6.0.0
|
||||
packaging>=23.2
|
||||
mozjpeg-lossless-optimization>=1.2.0
|
||||
natsort>=8.4.0
|
||||
distro>=1.8.0
|
||||
numpy==1.23.0
|
||||
PyMuPDF>=1.16
|
||||
@@ -9,4 +9,4 @@ mozjpeg-lossless-optimization>=1.2.0
|
||||
natsort>=8.4.0
|
||||
distro>=1.8.0
|
||||
numpy>=1.22.4
|
||||
PyMuPDF>=1.26.1
|
||||
PyMuPDF>=1.18.0
|
||||
|
||||
18
setup.py
18
setup.py
@@ -38,10 +38,17 @@ class BuildBinaryCommand(setuptools.Command):
|
||||
if sys.platform == 'darwin':
|
||||
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')
|
||||
min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET')
|
||||
if min_os:
|
||||
os.system(f'appdmg kcc.json dist/kcc_osx_{min_os.replace(".", "_")}_legacy_{VERSION}.dmg')
|
||||
else:
|
||||
os.system(f'appdmg kcc.json dist/kcc_macos_{platform.processor()}_{VERSION}.dmg')
|
||||
sys.exit(0)
|
||||
elif sys.platform == 'win32':
|
||||
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n KCC_' + VERSION + ' -w --noupx kcc.py')
|
||||
if os.getenv('WINDOWS_7'):
|
||||
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n kcc_win7_' + VERSION + ' -w --noupx kcc.py')
|
||||
else:
|
||||
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(
|
||||
@@ -74,8 +81,9 @@ setuptools.setup(
|
||||
},
|
||||
packages=['kindlecomicconverter'],
|
||||
install_requires=[
|
||||
'pyside6>=6.5.1',
|
||||
'Pillow>=11.3.0',
|
||||
'pyside6>=6.0.0',
|
||||
'Pillow>=9.3.0',
|
||||
'PyMuPDF>=1.18.0',
|
||||
'psutil>=5.9.5',
|
||||
'python-slugify>=1.2.1,<9.0.0',
|
||||
'raven>=6.0.0',
|
||||
@@ -84,7 +92,7 @@ setuptools.setup(
|
||||
'natsort>=8.4.0',
|
||||
'distro',
|
||||
'numpy>=1.22.4',
|
||||
'PyMuPDF>=1.26.1',
|
||||
'PyMuPDF>=1.16.1',
|
||||
],
|
||||
classifiers=[],
|
||||
zip_safe=False,
|
||||
|
||||
Reference in New Issue
Block a user