1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-17 22:48:53 +00:00

Compare commits

..

39 Commits
v5.6.3 ... win7

Author SHA1 Message Date
Alex Xu
b2aaa9e8b0 maybe restore win7 2024-04-14 05:10:27 -07:00
VampiroMedicado
e8502f008a hide subprocess consoles on Windows (#656) 2024-01-01 18:11:55 -08:00
inganault
169a41e7d2 Add missing dependencies to setup.py (#653) 2023-12-29 19:00:24 -08:00
Alex Xu
57cf669cdd add to credits 2023-12-28 18:55:08 -08:00
Alex Xu
0b4f089b8e add .DS_Store to .gitignore 2023-12-23 09:35:12 -08:00
Alex Xu
7eb985337c add requests to setup.py 2023-12-23 09:10:31 -08:00
Alex Xu
d62690e8bf import subprocess 2023-12-21 13:40:48 -08:00
Alex Xu
3988f2012f fix p7zip-rar error and files with ' or " by using arg lists instead of strings (#633)
* Revert "Revert "fix files with ' or " by using arg lists instead of strings (#581)" (#628)"

This reverts commit b528dab711.

* handle FileNotFoundError

* modify unar handling

* remove unneeded utf-8 encoding

* dont uft-8 encode 7z

* remove utf-8 encoding from 7z calls

* don't extract stderr

* add extraction error

* edit error message

* remove debug

* remove kindlegen location from GUI

* remove comment
2023-12-21 12:52:35 -08:00
Alex Xu
6cdd9d5909 change kobo epub filenames (#616) 2023-12-21 12:51:26 -08:00
Alex Xu
c29a4beac9 use requests library 2023-12-21 12:44:29 -08:00
Alex Xu
5f7bdef325 bump to 5.6.5 2023-12-21 09:50:34 -08:00
Alex Xu
54b5d698ee fix access denied 2023-12-21 09:43:33 -08:00
Alex Xu
a99c63acea use os_sorted over natsorted 2023-12-21 09:43:07 -08:00
dependabot[bot]
251df2e7ba Bump github/codeql-action from 2 to 3
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-21 09:42:20 -08:00
dependabot[bot]
b1379b7c59 Bump actions/upload-artifact from 3 to 4
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-21 09:42:11 -08:00
Alex Xu
983bde1691 add fat to list of drives 2023-12-21 09:41:18 -08:00
dependabot[bot]
e79e5a311c Bump actions/setup-python from 4 to 5
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-10 20:02:36 -08:00
jaroslawjanas
39c73dbc0f Added natsort to the dependency list in environment.yml 2023-12-10 19:59:33 -08:00
Alex Xu
915c9389ef remove kindlegen location UI 2023-12-09 17:39:01 -08:00
Alex Xu
89d887710e fix typo 2023-12-07 18:57:46 -08:00
Alex Xu
2e9bc5381a add PREREQUISITES 2023-12-07 18:56:57 -08:00
Alex Xu
ab1ce158c7 rename to prerequisites 2023-12-04 09:33:08 -08:00
Ciro Mattia Gonano
6278adfb25 Update issue templates 2023-11-29 12:40:11 +01:00
Alex Xu
6e6c13047e bump to 5.6.4 2023-11-28 08:32:34 -08:00
Alex Xu
b528dab711 Revert "fix files with ' or " by using arg lists instead of strings (#581)" (#628)
This reverts commit 431862a2e9.
2023-11-28 08:20:14 -08:00
Alex Xu
2ffefee928 link to readme not wiki for 7z kindlegen (#627)
* link to readme not wiki for 7z kindlegen

* update 7z link
2023-11-28 08:15:59 -08:00
Alex Xu
a5e5407363 streamline downloads and installation readme (#623)
* streamline downloads and installation

move linux stuff into wiki

* Update README.md

* update labels

* simplify
2023-11-28 07:38:46 -08:00
Alex Xu
da1ba64bd2 skip bookmarks if split (#620) 2023-11-28 06:54:47 -08:00
Alex Xu
6dcaf9a6d1 fix unsupported archive error (#619) 2023-11-28 06:53:58 -08:00
Alex Xu
3090a47f20 change mac pyinstaller -F to -D (#621) 2023-11-28 06:52:39 -08:00
Alex Xu
1e537915d4 make c2e and c2p versions first in file order 2023-11-28 06:50:55 -08:00
dependabot[bot]
7273ca25b8 Bump actions/setup-node from 3 to 4 (#609)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-09 23:06:46 +00:00
Alex Xu
68da853e42 fix page order in pdf and more by using natural sort instead of python sort (#589)
* use natsorted

* Add fast
2023-11-09 23:00:44 +00:00
Alex Xu
431862a2e9 fix files with ' or " by using arg lists instead of strings (#581)
* replace popen with subprocess run

* add splitlines

* remove stdin

* fix xml

* fix error logging and 7zip
2023-11-09 22:27:09 +00:00
Alex Xu
65062f8984 add Kindle 4/5/7/8/10 label (#614) 2023-11-09 22:15:34 +00:00
Alex Xu
8122fa1e45 add c2e and c2p note (#606) 2023-10-26 19:29:16 +00:00
Alex Xu
13fedff77b lower auto crop threshold (#598) 2023-10-26 19:20:26 +00:00
dependabot[bot]
61b1207a3e Bump actions/checkout from 3 to 4 (#592)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-16 12:10:53 +00:00
Alex Xu
60e9f075b8 replace exit() with sys.exit() (#590) 2023-09-08 20:18:32 +00:00
21 changed files with 171 additions and 160 deletions

31
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
Add a screenshot of your KCC settings.
**Desktop (please complete the following information):**
- OS: [e.g. macOS, Linux, Windows 11]
- Device [e.g. Kindle Paperwhite 3rd gen, Kobo Libra 2]
**Additional context**
Add any other context about the problem here.

View File

@@ -38,11 +38,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -69,6 +69,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v3
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@@ -25,9 +25,9 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: 3.11 python-version: 3.11
cache: 'pip' cache: 'pip'
@@ -59,7 +59,7 @@ jobs:
env: env:
UPDATE_INFO: gh-releases-zsync|ciromattia|kcc|latest|*x86_64.AppImage.zsync UPDATE_INFO: gh-releases-zsync|ciromattia|kcc|latest|*x86_64.AppImage.zsync
- name: upload artifact - name: upload artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: AppImage name: AppImage
path: './*.AppImage*' path: './*.AppImage*'

View File

@@ -25,9 +25,9 @@ jobs:
build: build:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: 3.11 python-version: 3.11
cache: 'pip' cache: 'pip'
@@ -66,7 +66,7 @@ jobs:
# apply provisioning profile # apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- uses: actions/setup-node@v3 - uses: actions/setup-node@v4
with: with:
node-version: 16 node-version: 16
- run: npm install -g appdmg - run: npm install -g appdmg
@@ -75,7 +75,7 @@ jobs:
run: | run: |
python setup.py build_binary python setup.py build_binary
- name: upload build - name: upload build
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: mac-os-build name: mac-os-build
path: dist/*.dmg path: dist/*.dmg

View File

@@ -12,7 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
# - name: Set up Python # - name: Set up Python
# uses: actions/setup-python@v4 # uses: actions/setup-python@v4
# with: # with:
@@ -44,10 +44,10 @@ jobs:
run: | run: |
version_built=$(cat kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/[^.0-9b]//g") version_built=$(cat kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/[^.0-9b]//g")
mv dist/windows/kcc.exe dist/windows/kcc_${version_built}.exe mv dist/windows/kcc.exe dist/windows/kcc_${version_built}.exe
mv dist/windows/kcc-c2e.exe dist/windows/kcc-c2e_${version_built}.exe mv dist/windows/kcc-c2e.exe dist/windows/kcc_c2e_${version_built}.exe
mv dist/windows/kcc-c2p.exe dist/windows/kcc-c2p_${version_built}.exe mv dist/windows/kcc-c2p.exe dist/windows/kcc_c2p_${version_built}.exe
- name: upload build - name: upload build
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: windows-build name: windows-build
path: dist/windows/*.exe path: dist/windows/*.exe
@@ -60,4 +60,4 @@ jobs:
files: | files: |
CHANGELOG.md CHANGELOG.md
LICENSE.txt LICENSE.txt
dist/windows/*.exe dist/windows/*.exe

View File

@@ -25,11 +25,11 @@ jobs:
build: build:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: 3.11 python-version: 3.8
cache: 'pip' cache: 'pip'
- name: Install dependencies - name: Install dependencies
env: env:
@@ -42,7 +42,7 @@ jobs:
run: | run: |
python setup.py build_binary python setup.py build_binary
- name: upload build - name: upload build
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: windows-build name: windows-build
path: dist/*.exe path: dist/*.exe
@@ -55,4 +55,4 @@ jobs:
files: | files: |
CHANGELOG.md CHANGELOG.md
LICENSE.txt LICENSE.txt
dist/*.exe dist/*.exe

1
.gitignore vendored
View File

@@ -11,3 +11,4 @@ KindleComicConverter*.egg-info/
/venv/ /venv/
/kindlegen* /kindlegen*
/kcc.bat /kcc.bat
.DS_Store

View File

@@ -28,11 +28,11 @@ If you find **KCC** valuable you can consider donating to the authors:
- Paweł Jastrzębski: - Paweł Jastrzębski:
- [![Donate PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS) - [![Donate PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YTTJ4LK2JDHPS)
- [![Donate Bitcoin](https://img.shields.io/badge/Donate-Bitcoin-green.svg)](https://jastrzeb.ski/donate/) - [![Donate Bitcoin](https://img.shields.io/badge/Donate-Bitcoin-green.svg)](https://jastrzeb.ski/donate/)
- Alex Xu
- [![Donate PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate/?business=QFJVE7A6LCP6U&no_recurring=0&item_name=Kindle+Comic+Converter&currency_code=USD)
## INSTALLATION ## DOWNLOADS
### DOWNLOADS
- **https://github.com/ciromattia/kcc/releases** - **https://github.com/ciromattia/kcc/releases**
@@ -42,45 +42,41 @@ You probably want either
- `kcc_*.*.*.exe` (Windows) - `kcc_*.*.*.exe` (Windows)
- `KindleComicConverter_osx_*.*.*.dmg` (Mac) - `KindleComicConverter_osx_*.*.*.dmg` (Mac)
Installation Wiki: https://github.com/ciromattia/kcc/wiki/Installation The `c2e` and `c2p` versions are command line tools for power users.
- flatpak : https://flathub.org/apps/details/io.github.ciromattia.kcc On Windows 11, you may need to run in compatibility mode for an older Windows version.
- Docker: https://github.com/ciromattia/kcc/pkgs/container/kcc
### DEPENDENCIES On Mac, right click open to get past the security warning.
Following software is required to run Linux version of **KCC** and/or bare sources:
- Python 3.3+
- [PyQt5](https://pypi.python.org/pypi/PyQt5) 5.6.0+ (only needed for GUI)
- [Pillow](https://pypi.python.org/pypi/Pillow/) 4.0.0+ (5.2.0+ needed for WebP support)
- [psutil](https://pypi.python.org/pypi/psutil) 5.0.0+
- [python-slugify](https://pypi.python.org/pypi/python-slugify) 1.2.1+, <8.0.0
- [raven](https://pypi.python.org/pypi/raven) 6.0.0+ (only needed for GUI)
On Debian based distributions these two commands should install all needed dependencies: For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation
## PREREQUISITES
You'll need to install various tools to access important but optional features.
```bash The installation process has been greatly streamlined. No need to add 7z to PATH or locate KindleGen from the internet and put it in a special folder with KCC. Just run it and KCC will tell you what to install.
$ sudo apt-get install -y python3 python3-dev libpng-dev libjpeg-dev p7zip-full p7zip-rar unrar-free libgl1 python3-pyqt5 && \
python -m pip install --upgrade pip && \ ### 7-Zip
python -m pip install --upgrade -r requirements.txt
#### Windows 7-Zip
First install 7z from https://www.7-zip.org/ or with command line:
```
winget install --id 7zip.7zip
``` ```
#### macOS 7-Zip/Unar
#### Optional dependencies with [Homebrew](https://brew.sh/) installed
- Qt platform integration plugin for Deepin Desktop Environment ```
```bash brew install p7zip
$ sudo apt-get install qt5dxcb-plugin brew install unar
``` ```
- KindleGen ~~[(deprecated link)](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211)~~ v2.9+ (For MOBI generation) ### KindleGen
- should be placed in a directory reachable by your _PATH_ or in _KCC_ directory
- `KindleGen` can be found in [Kindle Previewer](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011)
- `KindleGen` can be also be found in [Kindle Comic Creator](https://www.amazon.com/b?node=23496309011)
- [7z](http://www.7-zip.org/download.html) *(For CBZ/ZIP, CBR/RAR, 7z/CB7 support)*
- Unrar (no rar in 7z on Fedora)
#### Windows / macOS KindleGen
Install [Kindle Previewer 3 (KP3)](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011). KCC will automatically detect KindleGen from it.
## INPUT FORMATS ## INPUT FORMATS
**KCC** can understand and convert, at the moment, the following input types: **KCC** can understand and convert, at the moment, the following input types:
@@ -215,7 +211,12 @@ OTHER:
``` ```
## CREDITS ## CREDITS
**KCC** is made by [Ciro Mattia Gonano](http://github.com/ciromattia), [Paweł Jastrzębski](http://github.com/AcidWeb) and [Darodi](http://github.com/darodi) . **KCC** is made by
- [Ciro Mattia Gonano](http://github.com/ciromattia)
- [Paweł Jastrzębski](http://github.com/AcidWeb)
- [Darodi](http://github.com/darodi)
- [Alex Xu](http://github.com/axu2)
This script born as a cross-platform alternative to `KindleComicParser` by **Dc5e** (published [here](http://www.mobileread.com/forums/showthread.php?t=192783)). This script born as a cross-platform alternative to `KindleComicParser` by **Dc5e** (published [here](http://www.mobileread.com/forums/showthread.php?t=192783)).

View File

@@ -9,6 +9,7 @@ dependencies:
- python-slugify>=1.2.1 - python-slugify>=1.2.1
- raven>=6.0.0 - raven>=6.0.0
- distro - distro
- natsort[fast]>=8.4.0
- pip - pip
- pip: - pip:
- mozjpeg-lossless-optimization>=1.1.2 - mozjpeg-lossless-optimization>=1.1.2

View File

@@ -22,7 +22,7 @@ import sys
if sys.version_info < (3, 8, 0): if sys.version_info < (3, 8, 0):
print('ERROR: This is a Python 3.8+ script!') print('ERROR: This is a Python 3.8+ script!')
exit(1) sys.exit(1)
from multiprocessing import freeze_support, set_start_method from multiprocessing import freeze_support, set_start_method
from kindlecomicconverter.startup import startC2E from kindlecomicconverter.startup import startC2E

View File

@@ -22,7 +22,7 @@ import sys
if sys.version_info < (3, 8, 0): if sys.version_info < (3, 8, 0):
print('ERROR: This is a Python 3.8+ script!') print('ERROR: This is a Python 3.8+ script!')
exit(1) sys.exit(1)
from multiprocessing import freeze_support, set_start_method from multiprocessing import freeze_support, set_start_method
from kindlecomicconverter.startup import startC2P from kindlecomicconverter.startup import startC2P

2
kcc.py
View File

@@ -22,7 +22,7 @@ import sys
if sys.version_info < (3, 8, 0): if sys.version_info < (3, 8, 0):
print('ERROR: This is a Python 3.8+ script!') print('ERROR: This is a Python 3.8+ script!')
exit(1) sys.exit(1)
# OS specific workarounds # OS specific workarounds
import os import os

View File

@@ -16,25 +16,25 @@
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
import json
import os import os
import re import re
import subprocess import subprocess
import sys import sys
from urllib.parse import unquote from urllib.parse import unquote
from urllib.request import urlretrieve, urlopen
from time import sleep from time import sleep
from shutil import move, rmtree from shutil import move, rmtree
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
import requests
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork
from xml.sax.saxutils import escape from xml.sax.saxutils import escape
from psutil import Popen, Process from psutil import Process
from copy import copy from copy import copy
from distutils.version import StrictVersion from distutils.version import StrictVersion
from raven import Client from raven import Client
from tempfile import gettempdir from tempfile import gettempdir
from .shared import md5Checksum, HTMLStripper, sanitizeTrace, walkLevel from .shared import md5Checksum, HTMLStripper, sanitizeTrace, walkLevel, subprocess_run_silent
from . import __version__ from . import __version__
from . import comic2ebook from . import comic2ebook
from . import metadata from . import metadata
@@ -139,10 +139,7 @@ class VersionThread(QtCore.QThread):
def run(self): def run(self):
try: try:
last_version_url = urlopen("https://api.github.com/repos/ciromattia/kcc/releases/latest") json_parser = requests.get("https://api.github.com/repos/ciromattia/kcc/releases/latest").json()
data = last_version_url.read()
encoding = last_version_url.info().get_content_charset('utf-8')
json_parser = json.loads(data.decode(encoding))
html_url = json_parser["html_url"] html_url = json_parser["html_url"]
latest_version = json_parser["tag_name"] latest_version = json_parser["tag_name"]
@@ -752,7 +749,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
def display_kindlegen_missing(self): def display_kindlegen_missing(self):
self.addMessage( self.addMessage(
'<a href="https://github.com/ciromattia/kcc/wiki/Installation#kindlegen"><b>Cannot find KindleGen</b></a>: MOBI conversion is unavailable!', '<a href="https://github.com/ciromattia/kcc#kindlegen"><b>Install KindleGen (link)</b></a> to enable MOBI conversion for Kindles!',
'error' 'error'
) )
@@ -839,32 +836,21 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
os.chmod('/usr/local/bin/kindlegen', 0o755) os.chmod('/usr/local/bin/kindlegen', 0o755)
except Exception: except Exception:
pass pass
kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) try:
kindleGenExitCode.communicate() versionCheck = subprocess_run_silent(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, encoding='UTF-8')
if kindleGenExitCode.returncode == 0:
self.kindleGen = True self.kindleGen = True
versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) for line in versionCheck.stdout.splitlines():
for line in versionCheck.stdout:
line = line.decode("utf-8")
if 'Amazon kindlegen' in line: if 'Amazon kindlegen' in line:
versionCheck = line.split('V')[1].split(' ')[0] versionCheck = line.split('V')[1].split(' ')[0]
if StrictVersion(versionCheck) < StrictVersion('2.9'): if StrictVersion(versionCheck) < StrictVersion('2.9'):
self.addMessage('Your <a href="https://www.amazon.com/b?node=23496309011">KindleGen</a>' self.addMessage('Your <a href="https://www.amazon.com/b?node=23496309011">KindleGen</a>'
' is outdated! MOBI conversion might fail.', 'warning') ' is outdated! MOBI conversion might fail.', 'warning')
break break
where_command = 'where kindlegen.exe' except FileNotFoundError:
if os.name == 'posix':
where_command = 'which kindlegen'
process = subprocess.run(where_command, stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
locations = process.stdout.decode('utf-8').split('\n')
self.addMessage(f"<b>KindleGen Found:</b> {locations[0]}", 'info')
else:
self.kindleGen = False self.kindleGen = False
if startup: if startup:
self.display_kindlegen_missing() self.display_kindlegen_missing()
def __init__(self, kccapp, kccwindow): def __init__(self, kccapp, kccwindow):
global APP, MW, GUI global APP, MW, GUI
APP = kccapp APP = kccapp
@@ -943,7 +929,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle PW 1/2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle PW 1/2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'KPW'}, 'DefaultUpscale': False, 'Label': 'KPW'},
"Kindle": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle 4/5/7/8/10": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K578'}, 'DefaultUpscale': False, 'Label': 'K578'},
"Kindle DX/DXG": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 2, "Kindle DX/DXG": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 2,
'DefaultUpscale': False, 'Label': 'KDX'}, 'DefaultUpscale': False, 'Label': 'KDX'},
@@ -1011,7 +997,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"Kindle Voyage", "Kindle Voyage",
"Kindle 2", "Kindle 2",
"Kindle 1", "Kindle 1",
"Kindle", "Kindle 4/5/7/8/10",
"Separator", "Separator",
"Kobo Aura", "Kobo Aura",
"Kobo Aura ONE", "Kobo Aura ONE",
@@ -1039,14 +1025,13 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.addMessage('Since you are a new user of <b>KCC</b> please see few ' self.addMessage('Since you are a new user of <b>KCC</b> please see few '
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.', '<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
'info') 'info')
process = Popen('7z', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) try:
process.communicate() subprocess_run_silent(['7z'], stdout=PIPE, stderr=STDOUT)
if process.returncode == 0 or process.returncode == 7:
self.sevenzip = True self.sevenzip = True
else: except FileNotFoundError:
self.sevenzip = False self.sevenzip = False
self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/Installation#7-zip">Cannot find 7z</a>!' self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
' CBZ/CBR/ZIP/etc processing disabled.', 'warning') ' to enable CBZ/CBR/ZIP/etc processing.', 'warning')
self.detectKindleGen(True) self.detectKindleGen(True)
APP.messageFromOtherInstance.connect(self.handleMessage) APP.messageFromOtherInstance.connect(self.handleMessage)

View File

@@ -1,4 +1,4 @@
__version__ = '5.6.3' __version__ = '5.6.5'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi' __copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'

View File

@@ -19,6 +19,9 @@
# #
import os import os
import pathlib
import re
import subprocess
import sys import sys
from argparse import ArgumentParser from argparse import ArgumentParser
from time import strftime, gmtime from time import strftime, gmtime
@@ -31,16 +34,17 @@ from tempfile import mkdtemp, gettempdir, TemporaryFile
from shutil import move, copytree, rmtree, copyfile from shutil import move, copytree, rmtree, copyfile
from multiprocessing import Pool from multiprocessing import Pool
from uuid import uuid4 from uuid import uuid4
from natsort import os_sorted
from slugify import slugify as slugify_ext from slugify import slugify as slugify_ext
from PIL import Image from PIL import Image
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from psutil import Popen, virtual_memory, disk_usage from psutil import virtual_memory, disk_usage
from html import escape as hescape from html import escape as hescape
try: try:
from PyQt5 import QtCore from PyQt5 import QtCore
except ImportError: except ImportError:
QtCore = None QtCore = None
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run_silent
from . import comic2panel from . import comic2panel
from . import image from . import image
from . import comicarchive from . import comicarchive
@@ -671,11 +675,9 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
filename = srcpath + tomenumber + ext filename = srcpath + tomenumber + ext
else: else:
if 'Ko' in options.profile and options.format == 'EPUB': if 'Ko' in options.profile and options.format == 'EPUB':
path = srcpath.split(os.path.sep) src = pathlib.Path(srcpath)
path[-1] = ''.join(e for e in path[-1].split('.')[0] if e.isalnum()) + tomenumber + ext name = re.sub(r'\W+', '_', src.stem) + tomenumber + ext
if not path[-1].split('.')[0]: filename = src.with_name(name)
path[-1] = 'KCCPlaceholder' + tomenumber + ext
filename = os.path.sep.join(path)
else: else:
filename = os.path.splitext(srcpath)[0] + tomenumber + ext filename = os.path.splitext(srcpath)[0] + tomenumber + ext
if os.path.isfile(filename): if os.path.isfile(filename):
@@ -726,7 +728,7 @@ def getComicInfo(path, originalpath):
options.authors.sort() options.authors.sort()
else: else:
options.authors = ['KCC'] options.authors = ['KCC']
if xml.data['Bookmarks']: if xml.data['Bookmarks'] and options.batchsplit == 0:
options.chapters = xml.data['Bookmarks'] options.chapters = xml.data['Bookmarks']
if xml.data['Summary']: if xml.data['Summary']:
options.summary = hescape(xml.data['Summary']) options.summary = hescape(xml.data['Summary'])
@@ -761,7 +763,7 @@ def getPanelViewSize(deviceres, size):
def sanitizeTree(filetree): def sanitizeTree(filetree):
chapterNames = {} chapterNames = {}
for root, dirs, files in os.walk(filetree, False): for root, dirs, files in os.walk(filetree, False):
for i, name in enumerate(sorted(files)): for i, name in enumerate(os_sorted(files)):
splitname = os.path.splitext(name) splitname = os.path.splitext(name)
# file needs kcc at front AND back to avoid renaming issues # file needs kcc at front AND back to avoid renaming issues
@@ -1101,17 +1103,17 @@ def checkTools(source):
source = source.upper() source = source.upper()
if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \ if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \
source.endswith('.ZIP') or source.endswith('.CBZ'): source.endswith('.ZIP') or source.endswith('.CBZ'):
process = Popen('7z', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) try:
process.communicate() subprocess_run_silent(['7z'], stdout=PIPE, stderr=STDOUT)
if process.returncode != 0 and process.returncode != 7: except FileNotFoundError:
print('ERROR: 7z is missing!') print('ERROR: 7z is missing!')
exit(1) sys.exit(1)
if options.format == 'MOBI': if options.format == 'MOBI':
kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) try:
kindleGenExitCode.communicate() subprocess_run_silent(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT)
if kindleGenExitCode.returncode != 0: except FileNotFoundError:
print('ERROR: KindleGen is missing!') print('ERROR: KindleGen is missing!')
exit(1) sys.exit(1)
def checkPre(source): def checkPre(source):
@@ -1265,10 +1267,9 @@ def makeMOBIWorker(item):
kindlegenError = '' kindlegenError = ''
try: try:
if os.path.getsize(item) < 629145600: if os.path.getsize(item) < 629145600:
output = Popen('kindlegen -dont_append_source -locale en "' + item + '"', output = subprocess_run_silent(['kindlegen', '-dont_append_source', '-locale', 'en', item],
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) stdout=PIPE, stderr=STDOUT, encoding='UTF-8')
for line in output.stdout: for line in output.stdout.splitlines():
line = line.decode('utf-8')
# ERROR: Generic error # ERROR: Generic error
if "Error(" in line: if "Error(" in line:
kindlegenErrorCode = 1 kindlegenErrorCode = 1
@@ -1279,7 +1280,6 @@ def makeMOBIWorker(item):
if kindlegenErrorCode > 0: if kindlegenErrorCode > 0:
break break
if ":I1036: Mobi file built successfully" in line: if ":I1036: Mobi file built successfully" in line:
output.communicate()
break break
else: else:
# ERROR: EPUB too big # ERROR: EPUB too big

View File

@@ -22,11 +22,13 @@ import os
import platform import platform
import subprocess import subprocess
import distro import distro
from psutil import Popen
from shutil import move from shutil import move
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from xml.dom.minidom import parseString from xml.dom.minidom import parseString
from xml.parsers.expat import ExpatError from xml.parsers.expat import ExpatError
from .shared import subprocess_run_silent
EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.'
class ComicArchive: class ComicArchive:
@@ -35,70 +37,54 @@ class ComicArchive:
self.type = None self.type = None
if not os.path.isfile(self.filepath): if not os.path.isfile(self.filepath):
raise OSError('File not found.') raise OSError('File not found.')
process = Popen('7z l -y -p1 "' + self.filepath + '"', stderr=STDOUT, stdout=PIPE, stdin=PIPE, shell=True) process = subprocess_run_silent(['7z', 'l', '-y', '-p1', self.filepath], stderr=STDOUT, stdout=PIPE)
for line in process.stdout: for line in process.stdout.splitlines():
if b'Type =' in line: if b'Type =' in line:
self.type = line.rstrip().decode().split(' = ')[1].upper() self.type = line.rstrip().decode().split(' = ')[1].upper()
break break
process.communicate()
if process.returncode != 0 and distro.id() == 'fedora': if process.returncode != 0 and distro.id() == 'fedora':
process = Popen('unrar l -y -p1 "' + self.filepath + '"', stderr=STDOUT, stdout=PIPE, stdin=PIPE, shell=True) process = subprocess_run_silent(['unrar', 'l', '-y', '-p1', self.filepath], stderr=STDOUT, stdout=PIPE)
for line in process.stdout: for line in process.stdout.splitlines():
if b'Details: ' in line: if b'Details: ' in line:
self.type = line.rstrip().decode().split(' ')[1].upper() self.type = line.rstrip().decode().split(' ')[1].upper()
break break
process.communicate()
if process.returncode != 0: if process.returncode != 0:
raise OSError('Archive is corrupted or encrypted.') raise OSError(EXTRACTION_ERROR)
elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']:
raise OSError('Unsupported archive format.')
elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']:
raise OSError('Unsupported archive format.')
def extract(self, targetdir): def extract(self, targetdir):
if not os.path.isdir(targetdir): if not os.path.isdir(targetdir):
raise OSError('Target directory doesn\'t exist.') raise OSError('Target directory doesn\'t exist.')
process = Popen('7z x -y -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' + targetdir + '" "' + process = subprocess_run_silent(['7z', 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.filepath],
self.filepath + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) stdout=PIPE, stderr=STDOUT)
process.communicate()
if process.returncode != 0 and distro.id() == 'fedora': if process.returncode != 0 and distro.id() == 'fedora':
process = Popen('unrar x -y -x__MACOSX -x.DS_Store -xthumbs.db -xThumbs.db "' + self.filepath + '" "' + process = subprocess_run_silent(['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.filepath, targetdir]
targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) , stdout=PIPE, stderr=STDOUT)
process.communicate()
if process.returncode != 0: if process.returncode != 0:
raise OSError('Failed to extract archive.') raise OSError(EXTRACTION_ERROR)
elif process.returncode != 0 and platform.system() == 'Darwin': elif process.returncode != 0 and platform.system() == 'Darwin':
process = subprocess.run(f"unar '{self.filepath}' -f -o '{targetdir}'", process = subprocess_run_silent(['unar', self.filepath, '-f', '-o', targetdir],
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) stdout=PIPE, stderr=STDOUT)
if process.returncode != 0:
raise Exception(process.stdout.decode("utf-8"))
elif process.returncode != 0: elif process.returncode != 0:
raise OSError('Failed to extract archive. Check if p7zip-rar is installed.') raise OSError(EXTRACTION_ERROR)
tdir = os.listdir(targetdir) tdir = os.listdir(targetdir)
if 'ComicInfo.xml' in tdir: if 'ComicInfo.xml' in tdir:
tdir.remove('ComicInfo.xml') tdir.remove('ComicInfo.xml')
if len(tdir) == 1 and os.path.isdir(os.path.join(targetdir, tdir[0])):
for f in os.listdir(os.path.join(targetdir, tdir[0])):
move(os.path.join(targetdir, tdir[0], f), targetdir)
os.rmdir(os.path.join(targetdir, tdir[0]))
return targetdir return targetdir
def addFile(self, sourcefile): def addFile(self, sourcefile):
if self.type in ['RAR', 'RAR5']: if self.type in ['RAR', 'RAR5']:
raise NotImplementedError raise NotImplementedError
process = Popen('7z a -y "' + self.filepath + '" "' + sourcefile + '"', process = subprocess_run_silent(['7z', 'a', '-y', self.filepath, sourcefile],
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) stdout=PIPE, stderr=STDOUT)
process.communicate()
if process.returncode != 0: if process.returncode != 0:
raise OSError('Failed to add the file.') raise OSError('Failed to add the file.')
def extractMetadata(self): def extractMetadata(self):
process = Popen('7z x -y -so "' + self.filepath + '" ComicInfo.xml', process = subprocess_run_silent(['7z', 'x', '-y', '-so', self.filepath, 'ComicInfo.xml'],
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) stdout=PIPE, stderr=STDOUT)
xml = process.communicate()
if process.returncode != 0: if process.returncode != 0:
raise OSError('Failed to extract archive.') raise OSError(EXTRACTION_ERROR)
try: try:
return parseString(xml[0]) return parseString(process.stdout)
except ExpatError: except ExpatError:
return None return None

View File

@@ -24,12 +24,7 @@ import mozjpeg_lossless_optimization
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
from .shared import md5Checksum from .shared import md5Checksum
# 0.045 was determined by AUTO_CROP_THRESHOLD = 0.015
# 1200 / 824 = 1.456 (Kindle DX resolution)
# 2250 / 1500 = 1.5 (Typical manga page resolution)
# 1.5 - 1.456 < 0.045
# 0.045 / 1.5 = 0.03 (So maximum 3% of is cropped)
AUTO_CROP_THRESHOLD = 0.045
class ProfileData: class ProfileData:

View File

@@ -31,8 +31,7 @@ class Kindle:
def findDevice(self): def findDevice(self):
for drive in reversed(psutil.disk_partitions(False)): for drive in reversed(psutil.disk_partitions(False)):
if (drive[2] == 'FAT32' and drive[3] == 'rw,removable') or \ if (drive[2] == 'FAT32' and drive[3] == 'rw,removable') or \
(drive[2] == 'vfat' and 'rw' in drive[3]) or \ (drive[2] in ('vfat', 'msdos', 'FAT') and 'rw' in drive[3]):
(drive[2] == 'msdos' and 'rw' in drive[3]):
if os.path.isdir(os.path.join(drive[1], 'system')) and \ if os.path.isdir(os.path.join(drive[1], 'system')) and \
os.path.isdir(os.path.join(drive[1], 'documents')): os.path.isdir(os.path.join(drive[1], 'documents')):
return drive[1] return drive[1]

View File

@@ -21,8 +21,10 @@
import os import os
from hashlib import md5 from hashlib import md5
from html.parser import HTMLParser from html.parser import HTMLParser
import subprocess
from distutils.version import StrictVersion from distutils.version import StrictVersion
from re import split from re import split
import sys
from traceback import format_tb from traceback import format_tb
@@ -133,4 +135,9 @@ def dependencyCheck(level):
missing.append('Pillow 5.2.0+') missing.append('Pillow 5.2.0+')
if len(missing) > 0: if len(missing) > 0:
print('ERROR: ' + ', '.join(missing) + ' is not installed!') print('ERROR: ' + ', '.join(missing) + ' is not installed!')
exit(1) sys.exit(1)
def subprocess_run_silent(command, **kwargs):
if (os.name == 'nt'):
kwargs.setdefault('creationflags', subprocess.CREATE_NO_WINDOW)
return subprocess.run(command, **kwargs)

View File

@@ -1,8 +1,10 @@
PyQt5>=5.6.0 PyQt5>=5.6.0
Pillow>=5.2.0 Pillow>=5.2.0
psutil>=5.0.0 psutil>=5.0.0
requests>=2.31.0
python-slugify>=1.2.1 python-slugify>=1.2.1
raven>=6.0.0 raven>=6.0.0
# PyQt5-tools # PyQt5-tools
mozjpeg-lossless-optimization>=1.1.2 mozjpeg-lossless-optimization>=1.1.2
natsort[fast]>=8.4.0
distro distro

View File

@@ -37,19 +37,19 @@ class BuildBinaryCommand(distutils.cmd.Command):
def run(self): def run(self):
VERSION = __version__ VERSION = __version__
if sys.platform == 'darwin': if sys.platform == 'darwin':
os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py') os.system('pyinstaller -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 # TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg') os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg')
exit(0) sys.exit(0)
elif sys.platform == 'win32': elif sys.platform == 'win32':
os.system('pyinstaller -y -F -i icons\\comic2ebook.ico -n KCC_' + VERSION + ' -w --noupx kcc.py') os.system('pyinstaller -y -F -i icons\\comic2ebook.ico -n KCC_' + VERSION + ' -w --noupx kcc.py')
exit(0) sys.exit(0)
elif sys.platform == 'linux': elif sys.platform == 'linux':
os.system( os.system(
'pyinstaller --hidden-import=queue -y -F -i icons/comic2ebook.ico -n kcc_linux_' + VERSION + ' kcc.py') 'pyinstaller --hidden-import=queue -y -F -i icons/comic2ebook.ico -n kcc_linux_' + VERSION + ' kcc.py')
exit(0) sys.exit(0)
else: else:
exit(0) sys.exit(0)
setuptools.setup( setuptools.setup(
@@ -80,7 +80,10 @@ setuptools.setup(
'psutil>=5.0.0', 'psutil>=5.0.0',
'python-slugify>=1.2.1,<9.0.0', 'python-slugify>=1.2.1,<9.0.0',
'raven>=6.0.0', 'raven>=6.0.0',
'requests>=2.31.0',
'mozjpeg-lossless-optimization>=1.1.2', 'mozjpeg-lossless-optimization>=1.1.2',
'natsort[fast]>=8.4.0',
'distro',
], ],
classifiers=[], classifiers=[],
zip_safe=False, zip_safe=False,