1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-18 06:58:58 +00:00

Compare commits

..

107 Commits

Author SHA1 Message Date
darodi
b8a5582431 update Dockerfile-base file version 2023-08-09 18:40:32 +02:00
Alex Xu
9e73f065ae Merge branch 'ciromattia:master' into pyqt6 2023-08-08 14:38:44 -07:00
Alex Xu
0d417b8e11 prioritize KC2 over KP3 kindlegen version (#574)
* prioritize KC2 over KP3

* remove link to buggy KP3 kindlegen

* prioritize KC2 since KP3 version is buggy as of v3.72

* add kindlegen install link

* update comments
2023-08-08 20:25:03 +00:00
Alex Xu
b40cfcd9a6 Merge branch 'ciromattia:master' into pyqt6 2023-08-07 20:55:52 -07:00
Alex Xu
6836c20377 Fix page order and bookmarks by renaming numerically (#507)
* disable slugify

* rename to digits

* add -kcc

* add ABC checks

* remove kobo

* don't use if statements

* rename to suffix

* re-order check

* only slugify directories

* add break

* add sorted

* fix kcc-b logic

* add kcc to front of filename

---------

Co-authored-by: Alexander Xu <alexanderx@qualtrics.com>
2023-08-07 18:22:46 +00:00
swaggy-p-jp
f75ea6dfe8 enable double spread on kindle (#567) 2023-08-07 18:07:00 +00:00
Alex Xu
77afa77d32 Display kindlegen path (#571)
* add %LOCALAPPDATA%\\Amazon\\KC2

* fix kindlegen link

* expandvars

* remove link

* print where kindlegen

* adjust where command

* remove platform
2023-08-07 17:53:38 +00:00
Alex Xu
f73d889b6e fix damaged 2023-08-06 20:11:00 +00:00
darodi
8e04ccde18 Fix Mac CBZ, CBR, damaged, cover upload 2023-08-06 21:53:37 +02:00
darodi
cc1e5db0aa Merge pull request #566 from axu2/mac
Fix Mac CBZ, CBR, damaged, cover upload
2023-08-06 19:43:54 +00:00
Alex Xu
c5c88095ee bump to 5.6.3 2023-08-06 21:39:40 +02:00
Alex Xu
1318b9c0f2 Fix Mac CBZ, CBR, damaged, cover upload
.decode("utf-8")

edit kindlegen text

adjust 7z text

add unar

delete spec

remove 7z binaries

change path priorities

add 7z location for m1

delete plist

fix mac cover upload
2023-08-06 21:39:40 +02:00
Alex Xu
9339abb267 skip single pixel images in PDF (#546)
* skip pixels hopefully

* add comments and reorder

* add constant
2023-08-05 15:37:00 +00:00
Alex Xu
154707a412 Merge pull request #559
* make scribe default to no upscale with manga mode
2023-08-05 15:18:25 +00:00
Alex Xu
42c79b326f remove mozJpeg 2023-07-31 08:25:31 -07:00
Alex Xu
4cb986c302 remove space 2023-07-25 14:10:09 -07:00
Alex Xu
69856c7f48 use same settings save location as qt5 2023-07-25 14:02:02 -07:00
Alex Xu
a3dd23ed40 Merge branch 'master' into pyqt6 2023-07-25 13:28:23 -07:00
darodi
217f571f3d refactor display when kindlegen is missing (#555)
display_kindlegen_missing
2023-07-25 19:31:02 +00:00
Alex Xu
531cea88e6 Add 7z and Kindle Previewer 3 to PATH in all scenarios (#560)
* add 7z and KP3 to path
---------

Co-authored-by: darodi <4682830+darodi@users.noreply.github.com>
2023-07-25 19:27:56 +00:00
Alex Xu
5a21ff0bcf fix state issue 2023-07-23 15:25:35 -07:00
Alex Xu
17e8ffb7c0 add warning text 2023-07-12 12:45:17 -07:00
Alex Xu
a5202458dc Merge pull request #553
* warn p7zip-rar

* replace strerror
2023-07-05 16:54:24 +00:00
Alex Xu
4499e8faa0 add mozjpeg to gitignore 2023-07-05 07:06:06 -07:00
Alex Xu
6ee685e64c Update README.md 2023-07-04 08:47:16 -07:00
Alex Xu
9f34df1414 Update README.md 2023-07-04 08:45:20 -07:00
Alex Xu
5902d88d98 remove dead download links (#544)
* remove old links
2023-07-04 10:48:04 +00:00
Alex Xu
6a6a363c47 Update package-linux.yml 2023-07-01 11:25:28 -07:00
Alex Xu
6ba2ef66ca Update README.md 2023-07-01 11:24:43 -07:00
Alex Xu
264184c7e0 Update Dockerfile-base 2023-07-01 11:24:13 -07:00
Alex Xu
7e62329a51 Update package-linux.yml 2023-07-01 11:22:18 -07:00
Alex Xu
7e82f492c6 Update package-linux.yml 2023-07-01 11:21:56 -07:00
Alex Xu
829334556f add mozJpeg warning 2023-07-01 08:31:56 -07:00
Alex Xu
5c1408e7b7 remove references to qt5 2023-06-30 10:57:27 -07:00
Alexander Xu
b51c87e3bc import CheckedState 2023-06-30 10:39:19 -07:00
Alexander Xu
ada3232a0a fix batch 2023-06-30 10:31:43 -07:00
Alexander Xu
44682156c9 add mozJpeg 2023-06-30 10:29:55 -07:00
Alexander Xu
242fd70b54 Add CheckState enums 2023-06-30 10:19:39 -07:00
Alexander Xu
3f2365c677 edit shared 2023-06-30 09:37:03 -07:00
Alexander Xu
f4e45e6052 change exec 2023-06-30 09:35:02 -07:00
Alexander Xu
fe81af831e add comment back 2023-06-30 09:30:50 -07:00
Alexander Xu
eeec0501f5 add spaces 2023-06-30 09:29:34 -07:00
Alexander Xu
3d0d615879 fix tray icon 2023-06-30 09:28:03 -07:00
Alexander Xu
82bc405f6f pyside6 2023-06-30 09:10:55 -07:00
Alexander Xu
b2a079c958 fix epub icon 2023-06-28 14:54:14 -07:00
Alex Xu
b37ea52c7d Merge branch 'master' into pyqt6 2023-06-28 14:52:20 -07:00
Alex Xu
e7e41715d0 rename binary releases to downloads (#545) 2023-06-27 16:59:33 +00:00
Alex Xu
62d1c7c488 add 7z wiki link (#543) 2023-06-12 20:16:26 +00:00
darodi
95678adfd6 change format labels in gui (#538) 2023-06-12 18:28:21 +00:00
Alex Xu
4923dac8f0 explicitly name disabled archives (#531)
* explicitly name disabled archives
2023-06-10 11:14:41 +00:00
Vinh Quang Tran
935727c1db Override book's title if Title is set in XML (#532)
* Allow overriding title if it is set in XML
2023-06-10 09:13:01 +00:00
Jaroslaw Janas
b0e38a700a Bump conda python to 3.11 (#524) 2023-05-22 19:46:51 +00:00
Alex Xu
d668883b6f initial upgrade 2023-05-21 17:13:39 -07:00
darodi
23961243b6 CHANGELOG.md 2023-05-14 19:25:07 +02:00
darodi
c98d6179c3 docker update (#521) 2023-05-14 17:10:40 +00:00
Alex Xu
37200bdca0 Crop images within 3% of device aspect ratio (#495)
* Crop images within 3% of device aspect ratio

* use pad instead of expand+fit

* reafactor threshold check

* remove default val
2023-05-14 16:34:11 +00:00
Jaroslaw Janas
0193bcd00a conda environment (#520) 2023-05-14 16:32:06 +00:00
darodi
0bbe9348a2 OptionParser to ArgumentParser (#517) 2023-05-14 16:31:45 +00:00
darodi
d16628dc59 last version check (#518) 2023-05-14 16:31:31 +00:00
darodi
85c1801417 change version 2023-05-13 18:40:24 +02:00
darodi
b28ee08d01 Merge pull request #515 from darodi/unrar_for_fedora
use unrar for fedora only
2023-05-13 16:25:06 +00:00
darodi
dba927c351 use unrar for fedora only 2023-05-13 18:24:18 +02:00
cookie99999
dd5fd621db Update README.md
Recommend to install p7zip-rar on Debian based systems, as it is needed to properly decompress rar files (As explained under "supported formats" on https://packages.debian.org/sid/p7zip-full).

(cherry picked from commit f3ee9b770b)
2023-05-13 18:20:01 +02:00
darodi
21a167b3ee limit kindle scribe image size to (1440, 1920) when using kindlegen (#516) 2023-05-13 15:50:27 +00:00
darodi
1c9eeee52d use unrar for fedora only 2023-05-13 14:11:50 +02:00
darodi
611ee31526 Merge pull request #513 from axu2/patch-3
add 7z to PATH
2023-05-13 01:46:01 +00:00
darodi
7c4fdf9d1a Merge pull request #514 from darodi/limit_kindle_scribe_size_with_kindlegen
limit kindle scribe image size to (1440, 1920) when using kindlegen
2023-05-13 01:44:12 +00:00
darodi
b14f59e77a limit kindle scribe image size to (1440, 1920) when using kindlegen 2023-05-13 03:40:52 +02:00
Alex Xu
b4ec0b4a74 update wording 2023-05-12 14:17:18 -07:00
Alex Xu
a90b4c82c5 add UI 7z path 2023-05-12 13:06:09 -07:00
Alex Xu
390d58bf08 add 7z to PATH 2023-05-12 12:20:37 -07:00
darodi
e1aa6cd0af Merge pull request #503 from darodi/epub_200MB_above_200MB
Even with EPUB-200MB option selected, created file is above 200MB
2023-04-17 17:50:42 +00:00
darodi
718fda2f0c Even with EPUB-200MB option selected, created file is above 200MB 2023-04-17 19:49:14 +02:00
darodi
9d7904f63b Merge pull request #502 from darodi/master_revert_499
revert #499
2023-04-17 17:46:57 +00:00
darodi
ec58964c7c Revert "Disable Panel View for kindle scribe"
This reverts commit 9bc1f92c8c.
2023-04-17 19:45:20 +02:00
darodi
0b687ebadc Merge pull request #499 from darodi/disable_panel_view_kindle_scribe
Disable Panel View for kindle scribe
2023-04-06 17:21:31 +00:00
darodi
9bc1f92c8c Disable Panel View for kindle scribe 2023-04-06 19:13:56 +02:00
darodi
56f6c6962f Merge pull request #491 from thatrobotdev/brew-gui-change
Updates GUI text for new Homebrew version
2023-03-27 22:48:27 +00:00
darodi
dfd15ab572 Updates GUI text for new Homebrew version 2023-03-28 00:46:25 +02:00
James Kerrane
5588ad9250 Updates GUI text for new Homebrew version 2023-03-28 00:46:25 +02:00
dependabot[bot]
0c2adb517e Update python-slugify requirement from <8.0.0,>=1.2.1 to >=1.2.1,<9.0.0 (#473)
Updates the requirements on [python-slugify](https://github.com/un33k/python-slugify) to permit the latest version.
- [Release notes](https://github.com/un33k/python-slugify/releases)
- [Changelog](https://github.com/un33k/python-slugify/blob/master/CHANGELOG.md)
- [Commits](https://github.com/un33k/python-slugify/compare/1.2.1...v8.0.0)

---
updated-dependencies:
- dependency-name: python-slugify
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-27 22:16:07 +00:00
Alex Xu
9ab1cd359c remove transparency from image info 2023-03-27 22:04:35 +00:00
darodi
41d24e77e1 GUI delete option checkBox 2023-03-02 23:42:11 +01:00
Constantin Hong
17206ddf8b GUI delete option checkBox (closes #458) (#488)
* gui/func: add delete button (closes ciromattia#458)

* Update KCC_ui.py

fix typo

* GUI delete option checkBox

---------

Co-authored-by: darodi <4682830+darodi@users.noreply.github.com>
2023-03-02 23:38:27 +01:00
darodi
896c05a72f Add command line executables to CI/pipelines (#487) 2023-03-02 21:07:45 +01:00
darodi
f83106f35b Merge pull request #485 from Constantin1489/master
comic2ebook/func: Add a delete option
2023-03-02 17:34:16 +01:00
darodi
dfca136a2d comic2ebook/func: Add a delete option and function (closes #458) 2023-03-02 17:31:33 +01:00
Constantin Hong
92ced5f415 comic2ebook/func: Add a delete option and function (closes #458) 2023-03-02 17:12:46 +01:00
darodi
d18275d525 supporting Kindle Previewer (#486) 2023-03-02 17:09:45 +01:00
dependabot[bot]
c049adc3a1 Bump actions/checkout from 2 to 3 (#484)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [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/v2...v3)

---
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-02-27 21:31:42 +01:00
darodi
a3ce26983e new AppImage (#483) 2023-02-23 18:26:05 +01:00
dependabot[bot]
fe8195cfed Bump actions/upload-artifact from 2 to 3 (#468)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v3)

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-15 20:38:25 +01:00
darodi
3a1737e8d0 supporting Kindle Previewer 2023-01-22 18:27:50 +01:00
darodi
fb0c0231f3 build pipeline : drop pypi 2023-01-22 17:42:09 +01:00
darodi
2e807e23e1 update README.md 2023-01-22 14:25:37 +01:00
darodi
27841abb83 Update README.md 2023-01-22 14:08:44 +01:00
darodi
b71d056559 prepare pipeline for signing 2023-01-22 10:30:06 +01:00
darodi
a822dfa3ae Version bump 2023-01-21 11:57:55 +01:00
clach04
70de379987 Document CLI tools debian quick install
Command Line Tools can be used with pre-built dependencies.
2023-01-21 11:36:52 +01:00
Frédéric Brière
16e275bb1f Added --croppingminimum option to set a cropping minimum area ratio
Closes #342
2023-01-21 11:30:30 +01:00
darodi
b225de7b97 Enable Synthetic Spreads for the Kobo Forma. 2023-01-21 11:20:14 +01:00
Michael Shavit
4a89446914 Enable Synthetic Spreads for the Kobo Forma.
Rotating the device into landscape mode will cause it to display two
pages side by side. This is particularly useful for input comics whose
spreads are already split into two files.

See https://github.com/kobolabs/epub-spec#synthetic-spreads for
additional information on synthetic spreads.

(Tiny drive-by: Add Kobo format to list of profiles for which the AUTO
format is EPUB)
2023-01-21 11:20:14 +01:00
StudioEtrange
64521de577 replace move with copyfile 2023-01-21 11:03:39 +01:00
catsout
38b14fd734 Using communicate instead of terminate (#459)
Fix kindlegen not clear temp files
2023-01-21 11:00:04 +01:00
Alice Charatonik
4fa72780a1 fix in fedora: 7z doesn't support rar archives, use unrar (#370)
see 
https://github.com/ttys3/fedora-rpm-p7zip
https://sourceforge.net/p/sevenzip/discussion/45798/thread/dc2d0438/#8f9f
2023-01-21 10:54:11 +01:00
Cory Kleinschmidt
c979486e28 Fix pillow backwards compatibility, add mozjpeg-lossless-optimization to setup.py (#461)
- Fix pillow backwards compatibility
- Add mozjpeg-lossless-optimization to setup.py
2023-01-21 00:35:59 +01:00
darodi
03bd67cf2f Update README.md 2023-01-20 13:08:20 +01:00
42 changed files with 13117 additions and 12955 deletions

View File

@@ -0,0 +1,34 @@
name: Docker base
on:
workflow_dispatch:
push:
tags: [ 'docker-base-*' ]
# Don't trigger if it's just a documentation update
paths-ignore:
- '**.md'
- '**.MD'
- '**.yml'
- 'docs/**'
- 'LICENSE'
- '.gitattributes'
- '.gitignore'
- '.dockerignore'
jobs:
build_and_push:
uses: sdr-enthusiasts/common-github-workflows/.github/workflows/build_and_push_image.yml@main
with:
docker_build_file: ./Dockerfile-base
platform_linux_arm32v7_enabled: true
platform_linux_arm64v8_enabled: true
platform_linux_amd64_enabled: true
push_enabled: true
build_nohealthcheck: false
ghcr_repo_owner: ${{ github.repository_owner }}
ghcr_repo: ${{ github.repository }}
build_latest: false
secrets:
ghcr_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -2,10 +2,7 @@ name: Docker
on: on:
workflow_dispatch: workflow_dispatch:
#schedule:
# - cron: '39 5 * * *'
push: push:
# branches: [ master, pipeline_test, docker_test ]
# Publish semver tags as releases. # Publish semver tags as releases.
tags: [ 'v*.*.*' ] tags: [ 'v*.*.*' ]

View File

@@ -7,7 +7,7 @@ on:
push: push:
tags: tags:
- "v*.*.*" - "v*.*.*"
# Don't trigger if it's just a documentation update # Don't trigger if it's just a documentation update
paths-ignore: paths-ignore:
- '**.md' - '**.md'
@@ -23,9 +23,9 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
@@ -34,18 +34,13 @@ jobs:
- name: Install python dependencies - name: Install python dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libpng-dev libjpeg-dev p7zip-full python3-pyqt5 squashfs-tools sudo apt-get install -y libpng-dev libjpeg-dev p7zip-full p7zip-rar python3-pip squashfs-tools libfuse2
python -m pip install --upgrade pip setuptools wheel pyinstaller python -m pip install --upgrade pip setuptools wheel certifi pyinstaller --no-binary pyinstaller
pip install -r requirements.txt python -m pip install -r requirements.txt
- name: build binary - name: build binary
run: | run: |
python setup.py build_binary python setup.py build_binary
chmod +x dist/kcc_linux* chmod +x dist/kcc_linux*
- name: upload build
uses: actions/upload-artifact@v3
with:
name: linux-build
path: dist/kcc_linux*
# issue with this action, disabled and commented out # issue with this action, disabled and commented out
# see https://github.com/AppImageCrafters/build-appimage/issues/5 # see https://github.com/AppImageCrafters/build-appimage/issues/5
# see https://appimage-builder.readthedocs.io/en/latest/intro/install.html#install-appimagetool # see https://appimage-builder.readthedocs.io/en/latest/intro/install.html#install-appimagetool
@@ -63,9 +58,8 @@ jobs:
appimage-builder --recipe AppImageBuilder.yml --skip-test appimage-builder --recipe AppImageBuilder.yml --skip-test
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@v2 uses: actions/upload-artifact@v3
with: with:
name: AppImage name: AppImage
path: './*.AppImage*' path: './*.AppImage*'
@@ -78,5 +72,4 @@ jobs:
files: | files: |
CHANGELOG.md CHANGELOG.md
LICENSE.txt LICENSE.txt
dist/kcc_linux*
*.AppImage* *.AppImage*

View File

@@ -25,7 +25,7 @@ jobs:
build: build:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
@@ -33,13 +33,45 @@ jobs:
cache: 'pip' cache: 'pip'
- name: Install python dependencies - name: Install python dependencies
run: | run: |
python -m pip install --upgrade pip setuptools wheel pyinstaller python -m pip install --upgrade pip setuptools wheel pyinstaller certifi
pip install -r requirements.txt pip install -r requirements.txt
- name: Install the Apple certificate and provisioning profile
# TODO signing
# https://federicoterzi.com/blog/automatic-code-signing-and-notarization-for-macos-apps-using-github-actions/
if: ${{ false }}
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
- run: npm install -g appdmg - run: npm install -g appdmg
- name: build binary - name: build binary
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
run: | run: |
python setup.py build_binary python setup.py build_binary
- name: upload build - name: upload build
@@ -56,4 +88,11 @@ jobs:
files: | files: |
CHANGELOG.md CHANGELOG.md
LICENSE.txt LICENSE.txt
dist/*.dmg dist/*.dmg
- name: Clean up keychain and provisioning profile
# TODO signing
if: ${{ false }}
# if: ${{ always() }}
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision

View File

@@ -0,0 +1,63 @@
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: build KCC for windows with docker
on:
workflow_dispatch:
push:
tags:
- "v*.*.*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# - name: Set up Python
# uses: actions/setup-python@v4
# with:
# python-version: 3.11
# cache: 'pip'
# - name: Install python dependencies
# run: |
# python -m pip install --upgrade pip setuptools wheel pyinstaller
# pip install -r requirements.txt
# - name: build binary
# run: |
# pyi-makespec -F -i icons\\comic2ebook.ico -n KCC_test -w --noupx kcc.py
- name: Package Application
uses: JackMcKew/pyinstaller-action-windows@main
with:
path: .
spec: ./kcc.spec
- name: Package Application
uses: JackMcKew/pyinstaller-action-windows@main
with:
path: .
spec: ./kcc-c2e.spec
- name: Package Application
uses: JackMcKew/pyinstaller-action-windows@main
with:
path: .
spec: ./kcc-c2p.spec
- name: rename binaries
run: |
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-c2e.exe dist/windows/kcc-c2e_${version_built}.exe
mv dist/windows/kcc-c2p.exe dist/windows/kcc-c2p_${version_built}.exe
- name: upload build
uses: actions/upload-artifact@v3
with:
name: windows-build
path: dist/windows/*.exe
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true
generate_release_notes: true
files: |
CHANGELOG.md
LICENSE.txt
dist/windows/*.exe

View File

@@ -25,16 +25,19 @@ jobs:
build: build:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: 3.11 python-version: 3.11
cache: 'pip' cache: 'pip'
- name: Install python dependencies - name: Install dependencies
env:
PYINSTALLER_COMPILE_BOOTLOADER: 1
run: | run: |
python -m pip install --upgrade pip setuptools wheel pyinstaller python -m pip install --upgrade pip setuptools wheel
pip install -r requirements.txt pip install -r requirements.txt
pip install certifi pyinstaller --no-binary pyinstaller
- name: build binary - name: build binary
run: | run: |
python setup.py build_binary python setup.py build_binary

View File

@@ -1,50 +0,0 @@
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Publish Python distributions to TestPyPI
on:
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:
release:
if: github.repository == 'darodi/kcc'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.11
cache: 'pip'
- name: Install python dependencies
run: |
python -m pip install --upgrade pip setuptools wheel pyinstaller
pip install -r requirements.txt
- name: Build Dist
run: |
sed -i "s#NAME = 'KindleComicConverter'#NAME = 'KindleComicConverterDarodi'#" setup.py
python setup.py sdist
- name: Release On TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
skip_existing: true
verbose: true
print_hash: true

View File

@@ -1,44 +0,0 @@
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Publish Python distributions to PyPI
on:
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:
release:
if: github.repository == 'ciromattia/kcc'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.11
cache: 'pip'
- name: Install python dependencies
run: |
python -m pip install --upgrade pip setuptools wheel pyinstaller
pip install -r requirements.txt
- name: Build Dist
run: |
python setup.py sdist
- name: Publish a Python distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}

1
.gitignore vendored
View File

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

View File

@@ -19,24 +19,21 @@ AppDir:
- amd64 - amd64
allow_unauthenticated: true allow_unauthenticated: true
sources: sources:
- sourceline: deb http://archive.ubuntu.com/ubuntu focal main restricted - sourceline: deb http://archive.ubuntu.com/ubuntu jammy main restricted
- sourceline: deb http://archive.ubuntu.com/ubuntu focal-updates main restricted - sourceline: deb http://archive.ubuntu.com/ubuntu jammy-updates main restricted
- sourceline: deb http://archive.ubuntu.com/ubuntu focal universe - sourceline: deb http://archive.ubuntu.com/ubuntu jammy universe
- sourceline: deb http://archive.ubuntu.com/ubuntu focal-updates universe - sourceline: deb http://archive.ubuntu.com/ubuntu jammy-updates universe
- sourceline: deb http://archive.ubuntu.com/ubuntu focal multiverse - sourceline: deb http://archive.ubuntu.com/ubuntu jammy multiverse
- sourceline: deb http://archive.ubuntu.com/ubuntu focal-updates multiverse - sourceline: deb http://archive.ubuntu.com/ubuntu jammy-updates multiverse
- sourceline: deb http://archive.ubuntu.com/ubuntu focal-backports main restricted - sourceline: deb http://archive.ubuntu.com/ubuntu jammy-backports main restricted
universe multiverse universe multiverse
- sourceline: deb http://security.ubuntu.com/ubuntu focal-security main restricted - sourceline: deb http://security.ubuntu.com/ubuntu jammy-security main restricted
- sourceline: deb http://security.ubuntu.com/ubuntu focal-security universe - sourceline: deb http://security.ubuntu.com/ubuntu jammy-security universe
- sourceline: deb http://security.ubuntu.com/ubuntu focal-security multiverse - sourceline: deb http://security.ubuntu.com/ubuntu jammy-security multiverse
include: include:
- zlib1g:amd64 - libc6:amd64
files: files:
include: include: []
- /lib/x86_64-linux-gnu/libGLX.so.0
- /lib/x86_64-linux-gnu/libGLdispatch.so.0
- /usr/lib/locale/locale-archive
exclude: exclude:
- usr/share/man - usr/share/man
- usr/share/doc/*/README.* - usr/share/doc/*/README.*

View File

@@ -1,5 +1,32 @@
# CHANGELOG # CHANGELOG
#### 5.6.2:
* build pipeline : drop pypi by @darodi in [#465](https://github.com/ciromattia/kcc/pull/465)
* supporting Kindle Previewer by @darodi in [#466](https://github.com/ciromattia/kcc/pull/466)
* Bump actions/upload-artifact from 2 to 3 by @dependabot in [#468](https://github.com/ciromattia/kcc/pull/468)
* new appImage by @darodi in [#483](https://github.com/ciromattia/kcc/pull/483)
* Bump actions/checkout from 2 to 3 by @dependabot in [#484](https://github.com/ciromattia/kcc/pull/484)
* supporting Kindle Previewer by @darodi in [#486](https://github.com/ciromattia/kcc/pull/486)
* comic2ebook/func: Add a delete option (closes #458) by @Constantin1489 in [#485](https://github.com/ciromattia/kcc/pull/485)
* Add command line executables to CI/pipelines by @darodi in [#487](https://github.com/ciromattia/kcc/pull/487)
* gui/func: Add a 'delete after conversion' button (closes #458) by @Constantin1489 in [#488](https://github.com/ciromattia/kcc/pull/488)
* fix crashes on png transparency by @axu2 in [#494](https://github.com/ciromattia/kcc/pull/494)
* Update python-slugify requirement from <8.0.0,>=1.2.1 to >=1.2.1,<9.0.0 by @dependabot in [#473](https://github.com/ciromattia/kcc/pull/473)
* Updates GUI text for new Homebrew version by @thatrobotdev in [#491](https://github.com/ciromattia/kcc/pull/491)
* Even with EPUB-200MB option selected, created file is above 200MB by @darodi in [#503](https://github.com/ciromattia/kcc/pull/503)
* limit kindle scribe image size to (1440, 1920) when using kindlegen by @darodi in [#514](https://github.com/ciromattia/kcc/pull/514)
* add 7z to PATH by @axu2 in [#513](https://github.com/ciromattia/kcc/pull/513)
* use unrar for fedora only by @darodi in [#515](https://github.com/ciromattia/kcc/pull/515)
#### 5.6.1:
* Fix pillow backwards compatibility, add mozjpeg-lossless-optimization to setup.py by @corylk in #461
* fix in fedora: 7z doesn't support rar archives, use unrar by @AlicesReflexion in #370
* Using communicate instead of terminate by @catsout in #459
* use copyfile and delete instead of shutil.move fix #386 by @StudioEtrange in #387
#### 5.6.0: #### 5.6.0:
* Fix Docker 7z missing [darodi/kcc#31](https://github.com/darodi/kcc/issues/31), thanks [@darodi](https://github.com/darodi) * Fix Docker 7z missing [darodi/kcc#31](https://github.com/darodi/kcc/issues/31), thanks [@darodi](https://github.com/darodi)
* update to python 3.11, thanks [@darodi](https://github.com/darodi) * update to python 3.11, thanks [@darodi](https://github.com/darodi)

View File

@@ -1,147 +1,5 @@
FROM --platform=linux/amd64 python:3.11-slim-buster as compile-amd64
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
COPY requirements.txt /opt/kcc/
ENV PATH="/opt/venv/bin:$PATH"
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
apt-get install -y libpng-dev libjpeg-dev p7zip-full unrar-free libgl1 python3-pyqt5 && \
python -m pip install --upgrade pip && \
python -m venv /opt/venv && \
python -m pip install -r /opt/kcc/requirements.txt
######################################################################################
FROM --platform=linux/arm64 python:3.11-slim-buster as compile-arm64
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
ENV LC_ALL=C.UTF-8 \
LANG=C.UTF-8 \
LANGUAGE=en_US:en
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN set -x && \
TEMP_PACKAGES=() && \
KEPT_PACKAGES=() && \
# Packages only required during build
TEMP_PACKAGES+=(build-essential) && \
TEMP_PACKAGES+=(cmake) && \
TEMP_PACKAGES+=(libfreetype6-dev) && \
TEMP_PACKAGES+=(libfontconfig1-dev) && \
TEMP_PACKAGES+=(libpng-dev) && \
TEMP_PACKAGES+=(libjpeg-dev) && \
TEMP_PACKAGES+=(libssl-dev) && \
TEMP_PACKAGES+=(libxft-dev) && \
TEMP_PACKAGES+=(make) && \
TEMP_PACKAGES+=(python3-dev) && \
TEMP_PACKAGES+=(python3-setuptools) && \
TEMP_PACKAGES+=(python3-wheel) && \
# Packages kept in the image
KEPT_PACKAGES+=(bash) && \
KEPT_PACKAGES+=(ca-certificates) && \
KEPT_PACKAGES+=(chrpath) && \
KEPT_PACKAGES+=(locales) && \
KEPT_PACKAGES+=(locales-all) && \
KEPT_PACKAGES+=(libfreetype6) && \
KEPT_PACKAGES+=(libfontconfig1) && \
KEPT_PACKAGES+=(p7zip-full) && \
KEPT_PACKAGES+=(python3) && \
KEPT_PACKAGES+=(python3-pip) && \
KEPT_PACKAGES+=(python-pyqt5) && \
KEPT_PACKAGES+=(qt5-default) && \
KEPT_PACKAGES+=(unrar-free) && \
# Install packages
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
${KEPT_PACKAGES[@]} \
${TEMP_PACKAGES[@]} \
&& \
# Install required python modules
python -m pip install --upgrade pip && \
# python -m pip install -r /opt/kcc/requirements.txt && \
python -m venv /opt/venv && \
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
######################################################################################
FROM --platform=linux/arm/v7 python:3.11-slim-buster as compile-armv7
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
ENV LC_ALL=C.UTF-8 \
LANG=C.UTF-8 \
LANGUAGE=en_US:en
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN set -x && \
TEMP_PACKAGES=() && \
KEPT_PACKAGES=() && \
# Packages only required during build
TEMP_PACKAGES+=(build-essential) && \
TEMP_PACKAGES+=(cmake) && \
TEMP_PACKAGES+=(libffi-dev) && \
TEMP_PACKAGES+=(libfreetype6-dev) && \
TEMP_PACKAGES+=(libfontconfig1-dev) && \
TEMP_PACKAGES+=(libpng-dev) && \
TEMP_PACKAGES+=(libjpeg-dev) && \
TEMP_PACKAGES+=(libssl-dev) && \
TEMP_PACKAGES+=(libxft-dev) && \
TEMP_PACKAGES+=(make) && \
TEMP_PACKAGES+=(python3-dev) && \
TEMP_PACKAGES+=(python3-setuptools) && \
TEMP_PACKAGES+=(python3-wheel) && \
# Packages kept in the image
KEPT_PACKAGES+=(bash) && \
KEPT_PACKAGES+=(ca-certificates) && \
KEPT_PACKAGES+=(chrpath) && \
KEPT_PACKAGES+=(locales) && \
KEPT_PACKAGES+=(locales-all) && \
KEPT_PACKAGES+=(libfreetype6) && \
KEPT_PACKAGES+=(libfontconfig1) && \
KEPT_PACKAGES+=(p7zip-full) && \
KEPT_PACKAGES+=(python3) && \
KEPT_PACKAGES+=(python3-pip) && \
KEPT_PACKAGES+=(python-pyqt5) && \
KEPT_PACKAGES+=(qt5-default) && \
KEPT_PACKAGES+=(unrar-free) && \
# Install packages
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
${KEPT_PACKAGES[@]} \
${TEMP_PACKAGES[@]} \
&& \
# Install required python modules
python -m pip install --upgrade pip && \
# python -m pip install -r /opt/kcc/requirements.txt && \
python -m venv /opt/venv && \
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
######################################################################################
FROM --platform=linux/amd64 python:3.11-slim-buster as build-amd64
COPY --from=compile-amd64 /opt/venv /opt/venv
FROM --platform=linux/arm64 python:3.11-slim-buster as build-arm64
COPY --from=compile-arm64 /opt/venv /opt/venv
FROM --platform=linux/arm/v7 python:3.11-slim-buster as build-armv7
COPY --from=compile-armv7 /opt/venv /opt/venv
######################################################################################
# Select final stage based on TARGETARCH ARG # Select final stage based on TARGETARCH ARG
FROM build-${TARGETARCH}${TARGETVARIANT} FROM ghcr.io/ciromattia/kcc:docker-base-20230809
LABEL com.kcc.name="Kindle Comic Converter" LABEL com.kcc.name="Kindle Comic Converter"
LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi" LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi"
LABEL org.opencontainers.image.description='Kindle Comic Converter' LABEL org.opencontainers.image.description='Kindle Comic Converter'
@@ -154,14 +12,8 @@ LABEL org.opencontainers.image.vendor='ciromattia'
LABEL org.opencontainers.image.licenses='ISC' LABEL org.opencontainers.image.licenses='ISC'
LABEL org.opencontainers.image.title="Kindle Comic Converter" LABEL org.opencontainers.image.title="Kindle Comic Converter"
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /app
COPY . /opt/kcc COPY . /opt/kcc
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \ RUN cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
apt-get install -y p7zip-full unrar-free && \
ln -s /app/kindlegen /bin/kindlegen && \
cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
ENTRYPOINT ["/opt/kcc/kcc-c2e.py"] ENTRYPOINT ["/opt/kcc/kcc-c2e.py"]
CMD ["-h"] CMD ["-h"]

160
Dockerfile-base Normal file
View File

@@ -0,0 +1,160 @@
FROM --platform=linux/amd64 python:3.11-slim-bullseye as compile-amd64
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
COPY requirements.txt /opt/kcc/
ENV PATH="/opt/venv/bin:$PATH"
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
apt-get install -y libpng-dev libjpeg-dev p7zip-full unrar-free libgl1 && \
python -m pip install --upgrade pip && \
python -m venv /opt/venv && \
python -m pip install -r /opt/kcc/requirements.txt
######################################################################################
FROM --platform=linux/arm64 python:3.11-slim-bullseye as compile-arm64
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
ENV LC_ALL=C.UTF-8 \
LANG=C.UTF-8 \
LANGUAGE=en_US:en
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN set -x && \
TEMP_PACKAGES=() && \
KEPT_PACKAGES=() && \
# Packages only required during build
TEMP_PACKAGES+=(build-essential) && \
TEMP_PACKAGES+=(cmake) && \
TEMP_PACKAGES+=(libfreetype6-dev) && \
TEMP_PACKAGES+=(libfontconfig1-dev) && \
TEMP_PACKAGES+=(libpng-dev) && \
TEMP_PACKAGES+=(libjpeg-dev) && \
TEMP_PACKAGES+=(libssl-dev) && \
TEMP_PACKAGES+=(libxft-dev) && \
TEMP_PACKAGES+=(make) && \
TEMP_PACKAGES+=(python3-dev) && \
TEMP_PACKAGES+=(python3-setuptools) && \
TEMP_PACKAGES+=(python3-wheel) && \
# Packages kept in the image
KEPT_PACKAGES+=(bash) && \
KEPT_PACKAGES+=(ca-certificates) && \
KEPT_PACKAGES+=(chrpath) && \
KEPT_PACKAGES+=(locales) && \
KEPT_PACKAGES+=(locales-all) && \
KEPT_PACKAGES+=(libfreetype6) && \
KEPT_PACKAGES+=(libfontconfig1) && \
KEPT_PACKAGES+=(p7zip-full) && \
KEPT_PACKAGES+=(python3) && \
KEPT_PACKAGES+=(python3-pip) && \
KEPT_PACKAGES+=(unrar-free) && \
# Install packages
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
${KEPT_PACKAGES[@]} \
${TEMP_PACKAGES[@]} \
&& \
# Install required python modules
python -m pip install --upgrade pip && \
# python -m pip install -r /opt/kcc/requirements.txt && \
python -m venv /opt/venv && \
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
######################################################################################
FROM --platform=linux/arm/v7 python:3.11-slim-bullseye as compile-armv7
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
ENV LC_ALL=C.UTF-8 \
LANG=C.UTF-8 \
LANGUAGE=en_US:en
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN set -x && \
TEMP_PACKAGES=() && \
KEPT_PACKAGES=() && \
# Packages only required during build
TEMP_PACKAGES+=(build-essential) && \
TEMP_PACKAGES+=(cmake) && \
TEMP_PACKAGES+=(libffi-dev) && \
TEMP_PACKAGES+=(libfreetype6-dev) && \
TEMP_PACKAGES+=(libfontconfig1-dev) && \
TEMP_PACKAGES+=(libpng-dev) && \
TEMP_PACKAGES+=(libjpeg-dev) && \
TEMP_PACKAGES+=(libssl-dev) && \
TEMP_PACKAGES+=(libxft-dev) && \
TEMP_PACKAGES+=(make) && \
TEMP_PACKAGES+=(python3-dev) && \
TEMP_PACKAGES+=(python3-setuptools) && \
TEMP_PACKAGES+=(python3-wheel) && \
# Packages kept in the image
KEPT_PACKAGES+=(bash) && \
KEPT_PACKAGES+=(ca-certificates) && \
KEPT_PACKAGES+=(chrpath) && \
KEPT_PACKAGES+=(locales) && \
KEPT_PACKAGES+=(locales-all) && \
KEPT_PACKAGES+=(libfreetype6) && \
KEPT_PACKAGES+=(libfontconfig1) && \
KEPT_PACKAGES+=(p7zip-full) && \
KEPT_PACKAGES+=(python3) && \
KEPT_PACKAGES+=(python3-pip) && \
KEPT_PACKAGES+=(unrar-free) && \
# Install packages
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
${KEPT_PACKAGES[@]} \
${TEMP_PACKAGES[@]} \
&& \
# Install required python modules
python -m pip install --upgrade pip && \
# python -m pip install -r /opt/kcc/requirements.txt && \
python -m venv /opt/venv && \
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
######################################################################################
FROM --platform=linux/amd64 python:3.11-slim-bullseye as build-amd64
COPY --from=compile-amd64 /opt/venv /opt/venv
FROM --platform=linux/arm64 python:3.11-slim-bullseye as build-arm64
COPY --from=compile-arm64 /opt/venv /opt/venv
FROM --platform=linux/arm/v7 python:3.11-slim-bullseye as build-armv7
COPY --from=compile-armv7 /opt/venv /opt/venv
######################################################################################
# Select final stage based on TARGETARCH ARG
FROM build-${TARGETARCH}${TARGETVARIANT}
LABEL com.kcc.name="Kindle Comic Converter base image"
LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi"
LABEL org.opencontainers.image.description='Kindle Comic Converter base image'
LABEL org.opencontainers.image.documentation='https://github.com/ciromattia/kcc'
LABEL org.opencontainers.image.source='https://github.com/ciromattia/kcc'
LABEL org.opencontainers.image.authors='darodi'
LABEL org.opencontainers.image.url='https://github.com/ciromattia/kcc'
LABEL org.opencontainers.image.documentation='https://github.com/ciromattia/kcc'
LABEL org.opencontainers.image.vendor='ciromattia'
LABEL org.opencontainers.image.licenses='ISC'
LABEL org.opencontainers.image.title="Kindle Comic Converter"
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /app
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
apt-get install -y p7zip-full unrar-free && \
ln -s /app/kindlegen /bin/kindlegen && \
echo docker-base-20230809 > /IMAGE_VERSION

365
README.md
View File

@@ -1,16 +1,10 @@
# KCC # KCC
[![GitHub release](https://img.shields.io/github/v/release/ciromattia/kcc?display_name=tag&include_prereleases&label=pre-release)](https://github.com/ciromattia/kcc/releases)
[![PyPI](https://img.shields.io/github/v/release/darodi/kcc?display_name=tag&include_prereleases&label=testPypi)](https://test.pypi.org/project/KindleComicConverterDarodi/)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ciromattia/kcc/docker-publish.yml?label=docker%20build)](https://github.com/darodi/kcc/pkgs/container/kcc)
[//]: # ([![AUR]&#40;https://img.shields.io/aur/version/kcc-beta.svg?color=orange&#41;]&#40;https://aur.archlinux.org/packages/kcc-beta&#41;)
[![GitHub release](https://img.shields.io/github/release/ciromattia/kcc.svg)](https://github.com/ciromattia/kcc/releases) [![GitHub release](https://img.shields.io/github/release/ciromattia/kcc.svg)](https://github.com/ciromattia/kcc/releases)
[![PyPI](https://img.shields.io/pypi/v/KindleComicConverter.svg)](https://pypi.python.org/pypi/KindleComicConverter) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ciromattia/kcc/docker-publish.yml?label=docker%20build)](https://github.com/ciromattia/kcc/pkgs/container/kcc)
[![AUR](https://img.shields.io/aur/version/kcc.svg)](https://aur.archlinux.org/packages/kcc/)
**Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ. **Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ.
It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is
@@ -38,235 +32,42 @@ If you find **KCC** valuable you can consider donating to the authors:
## INSTALLATION ## INSTALLATION
### DOWNLOADS
### BINARY RELEASES
You can find the latest binary at the following link: You can find the latest binary at the following link:
- **https://github.com/ciromattia/kcc/releases** - **https://github.com/ciromattia/kcc/releases**
- flatpak : https://flathub.org/apps/details/io.github.ciromattia.kcc
- Docker: https://github.com/ciromattia/kcc/pkgs/container/kcc
more information on [installation](https://github.com/ciromattia/kcc/wiki/Installation)
~~- **[Windows](http://kcc.iosphe.re/Windows/) (64-bit only)**~~
~~- **[macOS](http://kcc.iosphe.re/OSX/) (10.14+)**~~
~~- **Linux:** Currently unavailable.~~
#### MacOS installation
##### x86_64 version
see: KindleComicConverter_osx_*.dmg in **https://github.com/ciromattia/kcc/releases**
If you can't open the last beta and the OS says it's damaged, fix it with:
```
xattr -d com.apple.quarantine /Applications/Kindle\ Comic\ Converter.app
```
##### M1/M2 arm64 version
Building is not available in github on M1 arch.
See this building method, to build it locally:
_Originally posted by @celogeek in https://github.com/darodi/kcc/issues/4#issuecomment-1364553511_
If you can't open the last beta and the OS says it's damaged, fix it with:
```
xattr -d com.apple.quarantine /Applications/Kindle\ Comic\ Converter.app
```
Or you could also have a look at this other project:
https://github.com/celogeek/go-comic-converter
#### Linux installation
- make binary executable
```bash
$ chmod a+x kcc_linux
```
- install 7zip
```bash
$ sudo apt-get install -y p7zip-full
```
- Download kindlegen
```bash
$ wget -qO- https://archive.org/download/kindlegen_linux_2_6_i386_v2_9/kindlegen_linux_2.6_i386_v2_9.tar.gz | tar xvz kindlegen
```
- copy kindlegen into '/usr/local/bin' and grant execute permissions for MOBI conversion.
```bash
$ sudo cp -R kindlegen /usr/local/bin && sudo chmod a+x /usr/local/bin/kindlegen
```
- run with backend x11 or it might not work with fedora
```bash
$ GDK_BACKEND=x11 ./kcc_linux
```
### PYPI
**KCC** is also available on PyPI.
On Debian based distributions these two commands should install all needed dependencies:
```bash
$ sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full python3-pyqt5
$ pip3 install --user --upgrade pillow python-slugify psutil pyqt5 raven
```
beta version
```bash
$ pip install --index-url https://test.pypi.org/simple/ KindleComicConverterDarodi
```
stable version
```bash
$ pip install --user KindleComicConverter
```
### APPIMAGE
- install 7zip
```bash
$ sudo apt-get install -y p7zip-full
```
- Download kindlegen
```bash
$ wget -qO- https://archive.org/download/kindlegen_linux_2_6_i386_v2_9/kindlegen_linux_2.6_i386_v2_9.tar.gz | tar xvz kindlegen
```
- copy kindlegen into '/usr/local/bin' and grant execute permissions for MOBI conversion.
```bash
$ sudo cp -R kindlegen /usr/local/bin && sudo chmod a+x /usr/local/bin/kindlegen
```
- make appImage executable
```bash
$ chmod a+x kindleComicConverter-latest-x86_64.AppImage
```
- run with backend x11 or it might not work with fedora
```bash
$ GDK_BACKEND=x11 ./kindleComicConverter-latest-x86_64.AppImage
```
### DOCKER
install kindlegen in your working directory and get last docker image
```bash
$ wget -qO- https://archive.org/download/kindlegen_linux_2_6_i386_v2_9/kindlegen_linux_2.6_i386_v2_9.tar.gz | tar xvz kindlegen
```
```bash
$ docker pull ghcr.io/ciromattia/kcc:latest
```
execute kcc-c2e
```bash
$ docker run --rm -v "$(pwd):/app" ghcr.io/ciromattia/kcc:latest
```
example
```bash
$ docker run --rm -v "$(pwd):/app" ghcr.io/ciromattia/kcc:latest -p KPW5 ./1.cbz
```
execute kcc-c2p
```bash
$ docker run --entrypoint /opt/kcc/kcc-c2p.py --rm -v "$(pwd):/app" ghcr.io/ciromattia/kcc:latest
```
### DEPENDENCIES ### DEPENDENCIES
Following software is required to run Linux version of **KCC** and/or bare sources: Following software is required to run Linux version of **KCC** and/or bare sources:
- Python 3.3+ - Python 3.3+
- [PyQt5](https://pypi.python.org/pypi/PyQt5) 5.6.0+ (only needed for GUI) - [PySide6](https://pypi.org/project/PySide6/) 6.5.1+ (only needed for GUI)
- [Pillow](https://pypi.python.org/pypi/Pillow/) 4.0.0+ (5.2.0+ needed for WebP support) - [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+ - [psutil](https://pypi.python.org/pypi/psutil) 5.9.5+
- [python-slugify](https://pypi.python.org/pypi/python-slugify) 1.2.1+, <8.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) - [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: On Debian based distributions these two commands should install all needed dependencies:
```bash
$ sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full python3-pyqt5
$ pip3 install --user --upgrade pillow python-slugify psutil pyqt5 raven
```bash
$ sudo apt-get install -y python3 python3-dev libpng-dev libjpeg-dev p7zip-full p7zip-rar unrar-free libgl1 && \
python -m pip install --upgrade pip && \
python -m pip install --upgrade -r requirements.txt
``` ```
#### Optional dependencies #### Optional dependencies
- Qt platform integration plugin for Deepin Desktop Environment - KindleGen ~~[(deprecated link)](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211)~~ v2.9+ (For MOBI generation)
```bash - should be placed in a directory reachable by your _PATH_ or in _KCC_ directory
$ sudo apt-get install qt5dxcb-plugin - `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)
- KindleGen ~~[deprecated link](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211)~~ v2.9+ in a directory reachable by your _PATH_ or in _KCC_ directory *(For MOBI generation)
which offers a command line interface and supports [Linux](https://archive.org/details/kindlegen2.9) [(mirror1)](https://archive.org/download/kindlegen_linux_2_6_i386_v2_9/kindlegen_linux_2.6_i386_v2_9.tar.gz), [Mac OSX](https://web.archive.org/web/20190905040839/https://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211) and [Windows](https://archive.org/details/kindlegen_win32_v2_9) has been deprecated, but binaries can still be found on the internet.
- [7z](http://www.7-zip.org/download.html) *(For CBZ/ZIP, CBR/RAR, 7z/CB7 support)* - [7z](http://www.7-zip.org/download.html) *(For CBZ/ZIP, CBR/RAR, 7z/CB7 support)*
- Unrar (no rar in 7z on Fedora)
### INSTALL FROM SOURCES
_Originally posted by @hhtien1408 in https://github.com/ciromattia/kcc/issues/438#issuecomment-1281159452_
```bash
$ git clone https://github.com/ciromattia/kcc.git
```
On Debian based distributions these two commands should install all needed dependencies:
```bash
$ sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full python3-pyqt5
```
Then install the necessary packages. You can do it by running the following command. The requirements.txt file is inside this repository, you will see it when you clone the repo.
```bash
$ pip3 install -r 'requirements.txt'
```
This should install the required packages. You can check the version by running
```bash
$ pip3 freeze
```
If the packages are in the wrong version, you can try to upgrade them by running
```bash
$ pip3 install --upgrade name_of_the_package
```
Download kindlegen.
```bash
$ wget https://archive.org/download/kindlegen_linux_2_6_i386_v2_9/kindlegen_linux_2.6_i386_v2_9.tar.gz | tar xvzf kindlegen
```
Copy kindlegen into '/usr/local/bin' and grant execute permissions for MOBI conversion.
```bash
$ sudo cp -R '/home/user/Desktop/kindlegen' '/usr/local/bin'
$ sudo chmod +rwx '/usr/local/bin/kindlegen'
```
Run python file for KCC GUI
```bash
$ python3 kcc.py
```
If everything goes well, you now should be able to use it.
Create destop file in '~/.local/share/applications' with codes:
```
#!/usr/bin/env xdg-open
[Desktop Entry]
Type=Application
Name=Kindle Comic Converter
Icon=kcc
Exec=python3 '/home/user/kcc/kcc.py'
Terminal=false
StartupWMClass=kcc
Name[en_US]=Kindle Comic Converter
```
Copy icon file into '/home/user/.local/share/icons'
```bash
$ sudo cp -R 'icons/comic2ebook.png' '/home/user/.local/share/icons'
```
@@ -286,6 +87,10 @@ After completed conversion, you should find ready file alongside the original in
Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details. Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details.
CLI version of **KCC** is intended for power users. It allows using options that might not be compatible and decrease the quality of output. CLI version of **KCC** is intended for power users. It allows using options that might not be compatible and decrease the quality of output.
CLI version has reduced dependencies, on Debian based distributions this commands should install all needed dependencies:
```
sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugify
```
### Profiles: ### Profiles:
@@ -321,85 +126,81 @@ CLI version of **KCC** is intended for power users. It allows using options that
### Standalone `kcc-c2e.py` usage: ### Standalone `kcc-c2e.py` usage:
``` ```
Usage: kcc-c2e [options] comic_file|comic_folder usage: kcc-c2e [options] [input]
Options: MANDATORY:
MAIN: input Full path to comic folder or file(s) to be processed.
-p PROFILE, --profile=PROFILE
Device profile (Available options: K1, K2, K34, K578,
KDX, KPW, KPW5, KV, KO, K11, KS, KoMT, KoG, KoGHD,
KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoL, KoF, KoS,
KoE) [Default=KV]
-m, --manga-style Manga style (right-to-left reading and splitting)
-q, --hq Try to increase the quality of magnification
-2, --two-panel Display two not four panels in Panel View mode
-w, --webtoon Webtoon processing mode
--targetsize=TARGETSIZE
the maximal size of output file in MB. [Default=100MB
for webtoon and 400MB for others]
OUTPUT SETTINGS: MAIN:
-o OUTPUT, --output=OUTPUT -p PROFILE, --profile PROFILE
Output generated file to specified directory or file Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KPW5, KV, KO, K11, KS, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoL, KoF, KoS, KoE) [Default=KV]
-t TITLE, --title=TITLE -m, --manga-style Manga style (right-to-left reading and splitting)
Comic title [Default=filename or directory name] -q, --hq Try to increase the quality of magnification
-f FORMAT, --format=FORMAT -2, --two-panel Display two not four panels in Panel View mode
Output format (Available options: Auto, MOBI, EPUB, -w, --webtoon Webtoon processing mode
CBZ, KFX, MOBI+EPUB) [Default=Auto] --ts TARGETSIZE, --targetsize TARGETSIZE
-b BATCHSPLIT, --batchsplit=BATCHSPLIT the maximal size of output file in MB. [Default=100MB for webtoon and 400MB for others]
Split output into multiple files. 0: Don't split 1:
Automatic mode 2: Consider every subdirectory as
separate volume [Default=0]
PROCESSING: PROCESSING:
-n, --noprocessing Do not modify image and ignore any profil or -n, --noprocessing Do not modify image and ignore any profil or processing option
processing option -u, --upscale Resize images smaller than device's resolution
-u, --upscale Resize images smaller than device's resolution -s, --stretch Stretch images to device's resolution
-s, --stretch Stretch images to device's resolution -r SPLITTER, --splitter SPLITTER
-r SPLITTER, --splitter=SPLITTER Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]
Double page parsing mode. 0: Split 1: Rotate 2: Both -g GAMMA, --gamma GAMMA
[Default=0] Apply gamma correction to linearize the image [Default=Auto]
-g GAMMA, --gamma=GAMMA -c CROPPING, --cropping CROPPING
Apply gamma correction to linearize the image Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]
[Default=Auto] --cp CROPPINGP, --croppingpower CROPPINGP
-c CROPPING, --cropping=CROPPING
Set cropping mode. 0: Disabled 1: Margins 2: Margins +
page numbers [Default=2]
--cp=CROPPINGP, --croppingpower=CROPPINGP
Set cropping power [Default=1.0] Set cropping power [Default=1.0]
--blackborders Disable autodetection and force black borders --cm CROPPINGM, --croppingminimum CROPPINGM
--whiteborders Disable autodetection and force white borders Set cropping minimum area ratio [Default=0.0]
--forcecolor Don't convert images to grayscale --blackborders Disable autodetection and force black borders
--forcepng Create PNG files instead JPEG --whiteborders Disable autodetection and force white borders
--mozjpeg Create JPEG files using mozJpeg --forcecolor Don't convert images to grayscale
--maximizestrips Turn 1x4 strips to 2x2 strips --forcepng Create PNG files instead JPEG
--mozjpeg Create JPEG files using mozJpeg
--maximizestrips Turn 1x4 strips to 2x2 strips
-d, --delete Delete source file(s) or a directory. It's not recoverable.
CUSTOM PROFILE: OUTPUT SETTINGS:
--customwidth=CUSTOMWIDTH -o OUTPUT, --output OUTPUT
Output generated file to specified directory or file
-t TITLE, --title TITLE
Comic title [Default=filename or directory name]
-f FORMAT, --format FORMAT
Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB) [Default=Auto]
-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]
CUSTOM PROFILE:
--customwidth CUSTOMWIDTH
Replace screen width provided by device profile Replace screen width provided by device profile
--customheight=CUSTOMHEIGHT --customheight CUSTOMHEIGHT
Replace screen height provided by device profile Replace screen height provided by device profile
OTHER: OTHER:
-h, --help Show this help message and exit -h, --help Show this help message and exit
``` ```
### Standalone `kcc-c2p.py` usage: ### Standalone `kcc-c2p.py` usage:
``` ```
Usage: kcc-c2p [options] comic_folder usage: kcc-c2p [options] [input]
Options: MANDATORY:
MANDATORY: input Full path to comic folder(s) to be processed. Separate multiple inputs with spaces.
-y HEIGHT, --height=HEIGHT
MAIN:
-y HEIGHT, --height HEIGHT
Height of the target device screen Height of the target device screen
-i, --in-place Overwrite source directory -i, --in-place Overwrite source directory
-m, --merge Combine every directory into a single image before -m, --merge Combine every directory into a single image before splitting
splitting
OTHER: OTHER:
-d, --debug Create debug file for every split image -d, --debug Create debug file for every split image
-h, --help Show this help message and exit -h, --help Show this help message and exit
``` ```
## CREDITS ## CREDITS

15
environment.yml Normal file
View File

@@ -0,0 +1,15 @@
name: kcc
channels:
- conda-forge
- defaults
dependencies:
- python=3.11
- Pillow>=5.2.0
- psutil>=5.9.5
- python-slugify>=1.2.1
- raven>=6.0.0
- distro
- pip
- pip:
- mozjpeg-lossless-optimization>=1.1.2
- pyside6>=6.5.1

View File

@@ -1,11 +1,3 @@
pyside6-uic gui/KCC.ui > kindlecomicconverter/KCC_ui.py
REM install qt creator pyside6-uic gui/MetaEditor.ui > kindlecomicconverter/KCC_ui_editor.py
REM conda create -n qtenv python=3.7 pyside6-rcc gui/KCC.qrc > kindlecomicconverter/KCC_rc.py
REM conda activate qtenv
REM pip install PyQt5
pyuic5 gui/KCC.ui > kindlecomicconverter/KCC_ui.py
pyuic5 gui/MetaEditor.ui > kindlecomicconverter/KCC_ui_editor.py
pyrcc5 gui/KCC.qrc > kindlecomicconverter/KCC_rc.py

View File

@@ -1,10 +1,5 @@
#!/bin/sh #!/bin/sh
# PREPARE PYTHON ENV pyside6-uic gui/KCC.ui --from-imports > kindlecomicconverter/KCC_ui.py
# conda create -n pyqt5 python=3.7 pyside6-uic gui/MetaEditor.ui --from-imports > kindlecomicconverter/KCC_ui_editor.py
# source activate pyqt5 pyside6-rcc gui/KCC.qrc > kindlecomicconverter/KCC_rc.py
# pip install pyqt5
pyuic5 gui/KCC.ui --from-imports > kindlecomicconverter/KCC_ui.py
pyuic5 gui/MetaEditor.ui --from-imports > kindlecomicconverter/KCC_ui_editor.py
pyrcc5 gui/KCC.qrc > kindlecomicconverter/KCC_rc.py

View File

@@ -175,10 +175,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1">
<widget class="QCheckBox" name="deleteBox">
<property name="toolTip">
<string>Delete input file(s) or directory. It's not recoverable!</string>
</property>
<property name="text">
<string>Delete input</string>
</property>
</widget>
</item>
<item row="4" column="2"> <item row="4" column="2">
<widget class="QCheckBox" name="disableProcessingBox"> <widget class="QCheckBox" name="disableProcessingBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Do not process any image, ignore profil and processing options&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;pre style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Do not process any image, ignore profile and processing options&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Disable processing</string> <string>Disable processing</string>
@@ -257,7 +267,7 @@
<item> <item>
<widget class="QSlider" name="croppingPowerSlider"> <widget class="QSlider" name="croppingPowerSlider">
<property name="maximum"> <property name="maximum">
<number>10</number> <number>200</number>
</property> </property>
<property name="singleStep"> <property name="singleStep">
<number>1</number> <number>1</number>
@@ -600,6 +610,7 @@
<tabstop>croppingBox</tabstop> <tabstop>croppingBox</tabstop>
<tabstop>mozJpegBox</tabstop> <tabstop>mozJpegBox</tabstop>
<tabstop>maximizeStrips</tabstop> <tabstop>maximizeStrips</tabstop>
<tabstop>deleteBox</tabstop>
<tabstop>disableProcessingBox</tabstop> <tabstop>disableProcessingBox</tabstop>
<tabstop>editorButton</tabstop> <tabstop>editorButton</tabstop>
<tabstop>wikiButton</tabstop> <tabstop>wikiButton</tabstop>

View File

@@ -19,8 +19,9 @@
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
import sys import sys
if sys.version_info[0] != 3:
print('ERROR: This is Python 3 script!') if sys.version_info < (3, 8, 0):
print('ERROR: This is a Python 3.8+ script!')
exit(1) exit(1)
from multiprocessing import freeze_support, set_start_method from multiprocessing import freeze_support, set_start_method

39
kcc-c2e.spec Normal file
View File

@@ -0,0 +1,39 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['kcc-c2e.py'],
pathex=['.'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='kcc-c2e',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None , icon='icons\\comic2ebook.ico')

View File

@@ -19,8 +19,9 @@
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
import sys import sys
if sys.version_info[0] != 3:
print('ERROR: This is Python 3 script!') if sys.version_info < (3, 8, 0):
print('ERROR: This is a Python 3.8+ script!')
exit(1) exit(1)
from multiprocessing import freeze_support, set_start_method from multiprocessing import freeze_support, set_start_method

39
kcc-c2p.spec Normal file
View File

@@ -0,0 +1,39 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['kcc-c2p.py'],
pathex=['.'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='kcc-c2p',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None , icon='icons\\comic2ebook.ico')

45
kcc.py
View File

@@ -19,25 +19,48 @@
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
import sys import sys
if sys.version_info[0] != 3:
print('ERROR: This is Python 3 script!') if sys.version_info < (3, 8, 0):
print('ERROR: This is a Python 3.8+ script!')
exit(1) exit(1)
# OS specific workarounds # OS specific workarounds
import os import os
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
# prioritize KC2 since it optionally also installs KP3
mac_paths = [
'/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/MacOS',
'/Applications/Kindle Previewer 3.app/Contents/lib/fc/bin/',
]
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
os.environ['PATH'] = os.path.dirname(os.path.abspath(sys.executable)) + \ os.environ['PATH'] += os.pathsep + os.pathsep.join(mac_paths +
'/../Resources:/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/' \ [
'MacOS:/usr/local/bin:/usr/bin:/bin' '/opt/homebrew/bin',
os.chdir(os.path.dirname(os.path.abspath(sys.executable)) + '/../Resources') '/usr/local/bin',
else: '/usr/bin',
os.chdir(os.path.dirname(os.path.abspath(__file__))) '/bin',
elif sys.platform.startswith('win'): ]
if getattr(sys, 'frozen', False): )
os.chdir(os.path.dirname(os.path.abspath(sys.executable))) os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
else: else:
os.environ['PATH'] = os.path.dirname(os.path.abspath(__file__)) + '/other/windows/;' + os.environ['PATH'] os.environ['PATH'] += os.pathsep + os.pathsep.join(mac_paths)
os.chdir(os.path.dirname(os.path.abspath(__file__)))
elif sys.platform.startswith('win'):
# prioritize KC2 since it optionally also installs KP3
win_paths = [
os.path.expandvars('%LOCALAPPDATA%\\Amazon\\KC2'),
os.path.expandvars('%LOCALAPPDATA%\\Amazon\\Kindle Previewer 3\\lib\\fc\\bin\\'),
'C:\\Program Files\\7-Zip',
]
if getattr(sys, 'frozen', False):
os.environ['PATH'] += os.pathsep + os.pathsep.join(win_paths)
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
else:
os.environ['PATH'] += os.pathsep + os.pathsep.join(win_paths +
[
os.path.dirname(os.path.abspath(__file__)) + '/other/windows/',
]
)
os.chdir(os.path.dirname(os.path.abspath(__file__))) os.chdir(os.path.dirname(os.path.abspath(__file__)))
# Load additional Sentry configuration # Load additional Sentry configuration
# if getattr(sys, 'frozen', False): # if getattr(sys, 'frozen', False):

39
kcc.spec Normal file
View File

@@ -0,0 +1,39 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['kcc.py'],
pathex=['.'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='kcc',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None , icon='icons\\comic2ebook.ico')

View File

@@ -16,23 +16,26 @@
# 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 subprocess
import sys import sys
from urllib.parse import unquote from urllib.parse import unquote
from urllib.request import urlretrieve from urllib.request import 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
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork from PySide6 import QtGui, QtCore, QtWidgets, QtNetwork
from PySide6.QtCore import Qt
from xml.sax.saxutils import escape from xml.sax.saxutils import escape
from psutil import Popen, Process from psutil import Popen, 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 HTMLStripper, sanitizeTrace, walkLevel
from . import __version__ from . import __version__
from . import comic2ebook from . import comic2ebook
from . import metadata from . import metadata
@@ -42,7 +45,7 @@ from . import KCC_ui_editor
class QApplicationMessaging(QtWidgets.QApplication): class QApplicationMessaging(QtWidgets.QApplication):
messageFromOtherInstance = QtCore.pyqtSignal(bytes) messageFromOtherInstance = QtCore.Signal(bytes)
def __init__(self, argv): def __init__(self, argv):
QtWidgets.QApplication.__init__(self, argv) QtWidgets.QApplication.__init__(self, argv)
@@ -50,7 +53,7 @@ class QApplicationMessaging(QtWidgets.QApplication):
self._timeout = 1000 self._timeout = 1000
self._locked = False self._locked = False
socket = QtNetwork.QLocalSocket(self) socket = QtNetwork.QLocalSocket(self)
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly) socket.connectToServer(self._key, QtCore.QIODeviceBase.OpenModeFlag.WriteOnly)
if not socket.waitForConnected(self._timeout): if not socket.waitForConnected(self._timeout):
self._server = QtNetwork.QLocalServer(self) self._server = QtNetwork.QLocalServer(self)
self._server.newConnection.connect(self.handleMessage) self._server.newConnection.connect(self.handleMessage)
@@ -64,7 +67,7 @@ class QApplicationMessaging(QtWidgets.QApplication):
self._server.close() self._server.close()
def event(self, e): def event(self, e):
if e.type() == QtCore.QEvent.FileOpen: if e.type() == QtCore.QEvent.Type.FileOpen:
self.messageFromOtherInstance.emit(bytes(e.file(), 'UTF-8')) self.messageFromOtherInstance.emit(bytes(e.file(), 'UTF-8'))
return True return True
else: else:
@@ -80,7 +83,7 @@ class QApplicationMessaging(QtWidgets.QApplication):
def sendMessage(self, message): def sendMessage(self, message):
socket = QtNetwork.QLocalSocket(self) socket = QtNetwork.QLocalSocket(self)
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly) socket.connectToServer(self._key, QtCore.QIODeviceBase.OpenModeFlag.WriteOnly)
socket.waitForConnected(self._timeout) socket.waitForConnected(self._timeout)
socket.write(bytes(message, 'UTF-8')) socket.write(bytes(message, 'UTF-8'))
socket.waitForBytesWritten(self._timeout) socket.waitForBytesWritten(self._timeout)
@@ -88,46 +91,40 @@ class QApplicationMessaging(QtWidgets.QApplication):
class QMainWindowKCC(QtWidgets.QMainWindow): class QMainWindowKCC(QtWidgets.QMainWindow):
progressBarTick = QtCore.pyqtSignal(str) progressBarTick = QtCore.Signal(str)
modeConvert = QtCore.pyqtSignal(int) modeConvert = QtCore.Signal(int)
addMessage = QtCore.pyqtSignal(str, str, bool) addMessage = QtCore.Signal(str, str, bool)
addTrayMessage = QtCore.pyqtSignal(str, str) addTrayMessage = QtCore.Signal(str, str)
showDialog = QtCore.pyqtSignal(str, str) showDialog = QtCore.Signal(str, str)
hideProgressBar = QtCore.pyqtSignal() hideProgressBar = QtCore.Signal()
forceShutdown = QtCore.pyqtSignal() forceShutdown = QtCore.Signal()
class Icons: class Icons:
def __init__(self): def __init__(self):
self.deviceKindle = QtGui.QIcon() self.deviceKindle = QtGui.QIcon()
self.deviceKindle.addPixmap(QtGui.QPixmap(":/Devices/icons/Kindle.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.deviceKindle.addPixmap(QtGui.QPixmap(":/Devices/icons/Kindle.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.deviceKobo = QtGui.QIcon() self.deviceKobo = QtGui.QIcon()
self.deviceKobo.addPixmap(QtGui.QPixmap(":/Devices/icons/Kobo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.deviceKobo.addPixmap(QtGui.QPixmap(":/Devices/icons/Kobo.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.deviceOther = QtGui.QIcon() self.deviceOther = QtGui.QIcon()
self.deviceOther.addPixmap(QtGui.QPixmap(":/Devices/icons/Other.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.deviceOther.addPixmap(QtGui.QPixmap(":/Devices/icons/Other.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.MOBIFormat = QtGui.QIcon() self.MOBIFormat = QtGui.QIcon()
self.MOBIFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/MOBI.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.MOBIFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/MOBI.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.CBZFormat = QtGui.QIcon() self.CBZFormat = QtGui.QIcon()
self.CBZFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/CBZ.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.CBZFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/CBZ.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.EPUBFormat = QtGui.QIcon() self.EPUBFormat = QtGui.QIcon()
self.EPUBFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/EPUB.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.EPUBFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/EPUB.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.KFXFormat = QtGui.QIcon()
self.KFXFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/KFX.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.MOBIEPUBFormat = QtGui.QIcon()
self.MOBIEPUBFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/MOBI.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.EPUB200MBFormat = QtGui.QIcon()
self.EPUB200MBFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/EPUB.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.info = QtGui.QIcon() self.info = QtGui.QIcon()
self.info.addPixmap(QtGui.QPixmap(":/Status/icons/info.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.info.addPixmap(QtGui.QPixmap(":/Status/icons/info.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.warning = QtGui.QIcon() self.warning = QtGui.QIcon()
self.warning.addPixmap(QtGui.QPixmap(":/Status/icons/warning.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.warning.addPixmap(QtGui.QPixmap(":/Status/icons/warning.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.error = QtGui.QIcon() self.error = QtGui.QIcon()
self.error.addPixmap(QtGui.QPixmap(":/Status/icons/error.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.error.addPixmap(QtGui.QPixmap(":/Status/icons/error.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.programIcon = QtGui.QIcon() self.programIcon = QtGui.QIcon()
self.programIcon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.programIcon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
class VersionThread(QtCore.QThread): class VersionThread(QtCore.QThread):
@@ -142,64 +139,27 @@ class VersionThread(QtCore.QThread):
self.wait() self.wait()
def run(self): def run(self):
# TODO adapt with github releases try:
pass last_version_url = urlopen("https://api.github.com/repos/ciromattia/kcc/releases/latest")
data = last_version_url.read()
encoding = last_version_url.info().get_content_charset('utf-8')
json_parser = json.loads(data.decode(encoding))
# try: html_url = json_parser["html_url"]
# XML = parse(urlopen(Request('https://kcc.iosphe.re/Version/', latest_version = json_parser["tag_name"]
# headers={'User-Agent': 'KindleComicConverter/' + __version__}))) latest_version = re.sub(r'^v', "", latest_version)
# except Exception:
# return if ("b" not in __version__ and StrictVersion(latest_version) > StrictVersion(__version__)) \
# latestVersion = XML.childNodes[0].getElementsByTagName('LatestVersion')[0].childNodes[0].toxml() or ("b" in __version__
# if ("beta" not in __version__ and StrictVersion(latestVersion) > StrictVersion(__version__)) \ and StrictVersion(latest_version) >= StrictVersion(re.sub(r'b.*', '', __version__))):
# or ("beta" in __version__ MW.addMessage.emit('<a href="' + html_url + '"><b>The new version is available!</b></a>', 'warning',
# and StrictVersion(latestVersion) >= StrictVersion(re.sub(r'-beta.*', '', __version__))): False)
# if sys.platform.startswith('win'): except Exception:
# self.newVersion = latestVersion return
# self.md5 = XML.childNodes[0].getElementsByTagName('MD5')[0].childNodes[0].toxml()
# MW.showDialog.emit('<b>New version released!</b> <a href="https://github.com/ciromattia/kcc/releases/">'
# 'See changelog.</a><br/><br/>Installed version: ' + __version__ +
# '<br/>Current version: ' + latestVersion +
# '<br/><br/>Would you like to start automatic update?', 'question')
# self.getNewVersion()
# else:
# MW.addMessage.emit('<a href="https://kcc.iosphe.re/">'
# '<b>The new version is available!</b></a> '
# '(<a href="https://github.com/ciromattia/kcc/releases/">'
# 'Changelog</a>)', 'warning', False)
def setAnswer(self, dialoganswer): def setAnswer(self, dialoganswer):
self.answer = dialoganswer self.answer = dialoganswer
def getNewVersion(self):
while self.answer is None:
sleep(1)
if self.answer == QtWidgets.QMessageBox.Yes:
try:
MW.modeConvert.emit(-1)
MW.progressBarTick.emit('Downloading update')
path = urlretrieve('https://kcc.iosphe.re/Windows/KindleComicConverter_win_' +
self.newVersion + '.exe', reporthook=self.getNewVersionTick)
if self.md5 != md5Checksum(path[0]):
raise Exception
move(path[0], path[0] + '.exe')
MW.hideProgressBar.emit()
MW.modeConvert.emit(1)
Popen(path[0] + '.exe /SP- /silent /noicons', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
MW.forceShutdown.emit()
except Exception:
MW.addMessage.emit('Failed to download the update!', 'warning', False)
MW.hideProgressBar.emit()
MW.modeConvert.emit(1)
def getNewVersionTick(self, size, blocksize, totalsize):
progress = int((size / (totalsize // blocksize)) * 100)
if size == 0:
MW.progressBarTick.emit('100')
if progress > self.barProgress:
self.barProgress = progress
MW.progressBarTick.emit('tick')
class ProgressThread(QtCore.QThread): class ProgressThread(QtCore.QThread):
def __init__(self): def __init__(self):
@@ -255,36 +215,37 @@ class WorkerThread(QtCore.QThread):
MW.modeConvert.emit(0) MW.modeConvert.emit(0)
parser = comic2ebook.makeParser() parser = comic2ebook.makeParser()
options, _ = parser.parse_args() options = parser.parse_args()
argv = '' argv = ''
currentJobs = [] currentJobs = []
options.profile = GUI.profiles[str(GUI.deviceBox.currentText())]['Label'] options.profile = GUI.profiles[str(GUI.deviceBox.currentText())]['Label']
options.format = str(GUI.formatBox.currentText()).replace('/AZW3', '') gui_current_format = GUI.formats[str(GUI.formatBox.currentText())]['format']
options.format = gui_current_format
if GUI.mangaBox.isChecked(): if GUI.mangaBox.isChecked():
options.righttoleft = True options.righttoleft = True
if GUI.rotateBox.checkState() == 1: if GUI.rotateBox.checkState() == Qt.CheckState.PartiallyChecked:
options.splitter = 2 options.splitter = 2
elif GUI.rotateBox.checkState() == 2: elif GUI.rotateBox.checkState() == Qt.CheckState.Checked:
options.splitter = 1 options.splitter = 1
if GUI.qualityBox.checkState() == 1: if GUI.qualityBox.checkState() == Qt.CheckState.PartiallyChecked:
options.autoscale = True options.autoscale = True
elif GUI.qualityBox.checkState() == 2: elif GUI.qualityBox.checkState() == Qt.CheckState.Checked:
options.hq = True options.hq = True
if GUI.webtoonBox.isChecked(): if GUI.webtoonBox.isChecked():
options.webtoon = True options.webtoon = True
if GUI.upscaleBox.checkState() == 1: if GUI.upscaleBox.checkState() == Qt.CheckState.PartiallyChecked:
options.stretch = True options.stretch = True
elif GUI.upscaleBox.checkState() == 2: elif GUI.upscaleBox.checkState() == Qt.CheckState.Checked:
options.upscale = True options.upscale = True
if GUI.gammaBox.isChecked() and float(GUI.gammaValue) > 0.09: if GUI.gammaBox.isChecked() and float(GUI.gammaValue) > 0.09:
options.gamma = float(GUI.gammaValue) options.gamma = float(GUI.gammaValue)
options.cropping = GUI.croppingBox.checkState() options.cropping = GUI.croppingBox.checkState().value
if GUI.croppingBox.checkState() >= 1: if GUI.croppingBox.checkState() != Qt.CheckState.Unchecked:
options.croppingp = float(GUI.croppingPowerValue) options.croppingp = float(GUI.croppingPowerValue)
if GUI.borderBox.checkState() == 1: if GUI.borderBox.checkState() == Qt.CheckState.PartiallyChecked:
options.white_borders = True options.white_borders = True
elif GUI.borderBox.checkState() == 2: elif GUI.borderBox.checkState() == Qt.CheckState.Checked:
options.black_borders = True options.black_borders = True
if GUI.outputSplit.isChecked(): if GUI.outputSplit.isChecked():
options.batchsplit = 2 options.batchsplit = 2
@@ -294,9 +255,11 @@ class WorkerThread(QtCore.QThread):
options.maximizestrips = True options.maximizestrips = True
if GUI.disableProcessingBox.isChecked(): if GUI.disableProcessingBox.isChecked():
options.noprocessing = True options.noprocessing = True
if GUI.mozJpegBox.checkState() == 1: if GUI.deleteBox.isChecked():
options.delete = True
if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked:
options.forcepng = True options.forcepng = True
elif GUI.mozJpegBox.checkState() == 2: elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
options.mozjpeg = True options.mozjpeg = True
if GUI.currentMode > 2: if GUI.currentMode > 2:
options.customwidth = str(GUI.widthBox.value()) options.customwidth = str(GUI.widthBox.value())
@@ -314,7 +277,7 @@ class WorkerThread(QtCore.QThread):
return return
self.errors = False self.errors = False
MW.addMessage.emit('<b>Source:</b> ' + job, 'info', False) MW.addMessage.emit('<b>Source:</b> ' + job, 'info', False)
if str(GUI.formatBox.currentText()) == 'CBZ': if gui_current_format == 'CBZ':
MW.addMessage.emit('Creating CBZ files', 'info', False) MW.addMessage.emit('Creating CBZ files', 'info', False)
GUI.progress.content = 'Creating CBZ files' GUI.progress.content = 'Creating CBZ files'
else: else:
@@ -364,11 +327,11 @@ class WorkerThread(QtCore.QThread):
return return
if not self.errors: if not self.errors:
GUI.progress.content = '' GUI.progress.content = ''
if str(GUI.formatBox.currentText()) == 'CBZ': if gui_current_format == 'CBZ':
MW.addMessage.emit('Creating CBZ files... <b>Done!</b>', 'info', True) MW.addMessage.emit('Creating CBZ files... <b>Done!</b>', 'info', True)
else: else:
MW.addMessage.emit('Creating EPUB files... <b>Done!</b>', 'info', True) MW.addMessage.emit('Creating EPUB files... <b>Done!</b>', 'info', True)
if str(GUI.formatBox.currentText()) == 'MOBI/AZW3' or str(GUI.formatBox.currentText()) == 'MOBI+EPUB': if 'MOBI' in gui_current_format:
MW.progressBarTick.emit('Creating MOBI files') MW.progressBarTick.emit('Creating MOBI files')
MW.progressBarTick.emit(str(len(outputPath) * 2 + 1)) MW.progressBarTick.emit(str(len(outputPath) * 2 + 1))
MW.progressBarTick.emit('tick') MW.progressBarTick.emit('tick')
@@ -469,7 +432,7 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
if self.isSystemTrayAvailable(): if self.isSystemTrayAvailable():
QtWidgets.QSystemTrayIcon.__init__(self, GUI.icons.programIcon, MW) self.setIcon(GUI.icons.programIcon)
self.activated.connect(self.catchClicks) self.activated.connect(self.catchClicks)
def catchClicks(self): def catchClicks(self):
@@ -478,7 +441,7 @@ class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
MW.activateWindow() MW.activateWindow()
def addTrayMessage(self, message, icon): def addTrayMessage(self, message, icon):
icon = eval('QtWidgets.QSystemTrayIcon.' + icon) icon = eval('QtWidgets.QSystemTrayIcon.MessageIcon.' + icon)
if self.supportsMessages() and not MW.isActiveWindow(): if self.supportsMessages() and not MW.isActiveWindow():
self.showMessage('Kindle Comic Converter', message, icon) self.showMessage('Kindle Comic Converter', message, icon)
@@ -504,7 +467,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf);;All (*.*)') 'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf);;All (*.*)')
else: else:
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, 'Comic (*.pdf);;All (*.*)') fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
'Comic (*.pdf);;All (*.*)')
for fname in fnames[0]: for fname in fnames[0]:
if fname != '': if fname != '':
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
@@ -587,7 +551,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.conversionAlive = False self.conversionAlive = False
self.worker.sync() self.worker.sync()
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
GUI.convertButton.setIcon(icon) GUI.convertButton.setIcon(icon)
GUI.convertButton.setText('Convert') GUI.convertButton.setText('Convert')
GUI.centralWidget.setAcceptDrops(True) GUI.centralWidget.setAcceptDrops(True)
@@ -595,7 +559,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.conversionAlive = True self.conversionAlive = True
self.worker.sync() self.worker.sync()
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
GUI.convertButton.setIcon(icon) GUI.convertButton.setIcon(icon)
GUI.convertButton.setText('Abort') GUI.convertButton.setText('Abort')
GUI.centralWidget.setAcceptDrops(False) GUI.centralWidget.setAcceptDrops(False)
@@ -615,7 +579,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
def togglecroppingBox(self, value): def togglecroppingBox(self, value):
if value: if value:
GUI.croppingWidget.setVisible(True) GUI.croppingWidget.setVisible(True)
else: else:
GUI.croppingWidget.setVisible(False) GUI.croppingWidget.setVisible(False)
self.changeCroppingPower(100) # 1.0 self.changeCroppingPower(100) # 1.0
@@ -681,6 +645,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if not GUI.webtoonBox.isChecked(): if not GUI.webtoonBox.isChecked():
GUI.qualityBox.setEnabled(profile['PVOptions']) GUI.qualityBox.setEnabled(profile['PVOptions'])
GUI.upscaleBox.setChecked(profile['DefaultUpscale']) GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
GUI.mangaBox.setChecked(True)
if not profile['PVOptions']: if not profile['PVOptions']:
GUI.qualityBox.setChecked(False) GUI.qualityBox.setChecked(False)
if str(GUI.deviceBox.currentText()) == 'Other': if str(GUI.deviceBox.currentText()) == 'Other':
@@ -695,7 +660,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.formatBox.setCurrentIndex(profile['DefaultFormat']) GUI.formatBox.setCurrentIndex(profile['DefaultFormat'])
if not GUI.webtoonBox.isChecked(): if not GUI.webtoonBox.isChecked():
GUI.qualityBox.setEnabled(profile['PVOptions']) GUI.qualityBox.setEnabled(profile['PVOptions'])
if str(GUI.formatBox.currentText()) == 'MOBI/AZW3': if GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'MOBI':
GUI.outputSplit.setEnabled(True) GUI.outputSplit.setEnabled(True)
else: else:
GUI.outputSplit.setEnabled(False) GUI.outputSplit.setEnabled(False)
@@ -726,11 +691,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
def showDialog(self, message, kind): def showDialog(self, message, kind):
if kind == 'error': if kind == 'error':
QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.Ok) QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.StandardButton.Ok)
elif kind == 'question': elif kind == 'question':
GUI.versionCheck.setAnswer(QtWidgets.QMessageBox.question(MW, 'KCC - Question', message, GUI.versionCheck.setAnswer(QtWidgets.QMessageBox.question(MW, 'KCC - Question', message,
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.Yes,
QtWidgets.QMessageBox.No)) QtWidgets.QMessageBox.No))
def updateProgressbar(self, command): def updateProgressbar(self, command):
if command == 'tick': if command == 'tick':
@@ -754,7 +719,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.conversionAlive = False self.conversionAlive = False
self.worker.sync() self.worker.sync()
else: else:
if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier:
dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath) dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath)
if dname != '': if dname != '':
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
@@ -777,22 +742,21 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.addMessage('Target resolution is not set!', 'error') self.addMessage('Target resolution is not set!', 'error')
self.needClean = True self.needClean = True
return return
if str(GUI.formatBox.currentText()) == 'MOBI/AZW3' and not self.kindleGen: if 'MOBI' in GUI.formats[str(GUI.formatBox.currentText())]['format'] and not self.kindleGen:
self.detectKindleGen() self.detectKindleGen()
if not self.kindleGen: if not self.kindleGen:
GUI.jobList.clear() GUI.jobList.clear()
self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=' self.display_kindlegen_missing()
'1000765211"><b>KindleGen</b></a>! MOBI conversion is unavailable!', 'error')
if sys.platform.startswith('win'):
self.addMessage('Download it and place EXE in KCC directory.', 'error')
elif sys.platform.startswith('darwin'):
self.addMessage('Install it using <a href="http://brew.sh/">Brew</a>.', 'error')
else:
self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')
self.needClean = True self.needClean = True
return return
self.worker.start() self.worker.start()
def display_kindlegen_missing(self):
self.addMessage(
'<a href="https://github.com/ciromattia/kcc/wiki/Installation#kindlegen"><b>Cannot find KindleGen</b></a>: MOBI conversion is unavailable!',
'error'
)
def saveSettings(self, event): def saveSettings(self, event):
if self.conversionAlive: if self.conversionAlive:
GUI.convertButton.setEnabled(False) GUI.convertButton.setEnabled(False)
@@ -808,22 +772,23 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.settings.setValue('currentFormat', GUI.formatBox.currentIndex()) self.settings.setValue('currentFormat', GUI.formatBox.currentIndex())
self.settings.setValue('startNumber', self.startNumber + 1) self.settings.setValue('startNumber', self.startNumber + 1)
self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height())) self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height()))
self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(), self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState().value,
'rotateBox': GUI.rotateBox.checkState(), 'rotateBox': GUI.rotateBox.checkState().value,
'qualityBox': GUI.qualityBox.checkState(), 'qualityBox': GUI.qualityBox.checkState().value,
'gammaBox': GUI.gammaBox.checkState(), 'gammaBox': GUI.gammaBox.checkState().value,
'croppingBox': GUI.croppingBox.checkState(), 'croppingBox': GUI.croppingBox.checkState().value,
'croppingPowerSlider': float(self.croppingPowerValue) * 100, 'croppingPowerSlider': float(self.croppingPowerValue) * 100,
'upscaleBox': GUI.upscaleBox.checkState(), 'upscaleBox': GUI.upscaleBox.checkState().value,
'borderBox': GUI.borderBox.checkState(), 'borderBox': GUI.borderBox.checkState().value,
'webtoonBox': GUI.webtoonBox.checkState(), 'webtoonBox': GUI.webtoonBox.checkState().value,
'outputSplit': GUI.outputSplit.checkState(), 'outputSplit': GUI.outputSplit.checkState().value,
'colorBox': GUI.colorBox.checkState(), 'colorBox': GUI.colorBox.checkState().value,
'disableProcessingBox': GUI.disableProcessingBox.checkState(), 'disableProcessingBox': GUI.disableProcessingBox.checkState().value,
'mozJpegBox': GUI.mozJpegBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState().value,
'widthBox': GUI.widthBox.value(), 'widthBox': GUI.widthBox.value(),
'heightBox': GUI.heightBox.value(), 'heightBox': GUI.heightBox.value(),
'maximizeStrips': GUI.maximizeStrips.checkState(), 'deleteBox': GUI.deleteBox.checkState().value,
'maximizeStrips': GUI.maximizeStrips.checkState().value,
'gammaSlider': float(self.gammaValue) * 100}) 'gammaSlider': float(self.gammaValue) * 100})
self.settings.sync() self.settings.sync()
self.tray.hide() self.tray.hide()
@@ -885,21 +850,21 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
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="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=' self.addMessage('Your <a href="https://www.amazon.com/b?node=23496309011">KindleGen</a>'
'1000765211">KindleGen</a> is outdated! MOBI conversion might fail.', 'warning') ' is outdated! MOBI conversion might fail.', 'warning')
break break
where_command = 'where kindlegen.exe'
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: else:
self.kindleGen = False self.kindleGen = False
if startup: if startup:
self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211">' self.display_kindlegen_missing()
'<b>KindleGen</b></a>! MOBI conversion will be unavailable!', 'error')
if sys.platform.startswith('win'):
self.addMessage('Download it and place EXE in KCC directory.', 'error')
elif sys.platform.startswith('darwin'):
self.addMessage('Install it using <a href="http://brew.sh/">Brew</a>: <i>brew cask install kindle-c'
'omic-creator</i>', 'error')
else:
self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')
def __init__(self, kccapp, kccwindow): def __init__(self, kccapp, kccwindow):
global APP, MW, GUI global APP, MW, GUI
@@ -949,6 +914,16 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if self.windowSize == '0x0': if self.windowSize == '0x0':
MW.resize(500, 500) MW.resize(500, 500)
self.formats = { # text, icon, data/option_format
"MOBI/AZW3": {'icon': 'MOBI', 'format': 'MOBI'},
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
"EPUB (Calibre KFX)": {'icon': 'EPUB', 'format': 'KFX'},
"MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'},
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'}
}
self.profiles = { self.profiles = {
"Kindle Oasis 2/3": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle Oasis 2/3": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KO'}, 'DefaultUpscale': True, 'Label': 'KO'},
@@ -957,7 +932,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle Scribe": { "Kindle Scribe": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'Label': 'KS', 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'Label': 'KS',
}, },
"Kindle 11": { "Kindle 11": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'Label': 'K11', 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'Label': 'K11',
@@ -1055,7 +1030,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'com/ciromattia/kcc/blob/master/README.md#issues--new-features--donations">DO' 'com/ciromattia/kcc/blob/master/README.md#issues--new-features--donations">DO'
'NATE</a> - <a href="http://www.mobileread.com/forums/showthread.php?t=207461' 'NATE</a> - <a href="http://www.mobileread.com/forums/showthread.php?t=207461'
'">FORUM</a></b>') '">FORUM</a></b>')
statusBarLabel.setAlignment(QtCore.Qt.AlignCenter) statusBarLabel.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
statusBarLabel.setOpenExternalLinks(True) statusBarLabel.setOpenExternalLinks(True)
GUI.statusBar.addPermanentWidget(statusBarLabel, 1) GUI.statusBar.addPermanentWidget(statusBarLabel, 1)
@@ -1071,8 +1046,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.sevenzip = True self.sevenzip = True
else: else:
self.sevenzip = False self.sevenzip = False
self.addMessage('Cannot find <a href="http://www.7-zip.org/download.html">7z</a>!' self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/Installation#7-zip">Cannot find 7z</a>!'
' Processing of archives will be disabled.', 'warning') ' CBZ/CBR/ZIP/etc processing disabled.', 'warning')
self.detectKindleGen(True) self.detectKindleGen(True)
APP.messageFromOtherInstance.connect(self.handleMessage) APP.messageFromOtherInstance.connect(self.handleMessage)
@@ -1113,9 +1088,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.deviceBox.addItem(self.icons.deviceKobo, profile) GUI.deviceBox.addItem(self.icons.deviceKobo, profile)
else: else:
GUI.deviceBox.addItem(self.icons.deviceKindle, profile) GUI.deviceBox.addItem(self.icons.deviceKindle, profile)
for f in ['MOBI/AZW3', 'EPUB', 'CBZ', 'KFX', 'MOBI+EPUB', 'EPUB-200MB']: for f in self.formats:
format_prefix = f.replace('/AZW3', '').replace('+', '').replace('-', '') GUI.formatBox.addItem(eval('self.icons.' + self.formats[f]['icon'] + 'Format'), f)
GUI.formatBox.addItem(eval('self.icons.' + format_prefix + 'Format'), f)
if self.lastDevice > GUI.deviceBox.count(): if self.lastDevice > GUI.deviceBox.count():
self.lastDevice = 0 self.lastDevice = 0
if profilesGUI[self.lastDevice] == "Separator": if profilesGUI[self.lastDevice] == "Separator":
@@ -1142,14 +1116,14 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
else: else:
try: try:
if eval('GUI.' + str(option)).isEnabled(): if eval('GUI.' + str(option)).isEnabled():
eval('GUI.' + str(option)).setCheckState(self.options[option]) eval('GUI.' + str(option)).setCheckState(Qt.CheckState(self.options[option]))
except AttributeError: except AttributeError:
pass pass
self.worker.sync() self.worker.sync()
self.versionCheck.start() self.versionCheck.start()
self.tray.show() self.tray.show()
# Cleanup unfisnished conversion # Cleanup unfinished conversion
for root, dirs, _ in walkLevel(gettempdir(), 0): for root, dirs, _ in walkLevel(gettempdir(), 0):
for tempdir in dirs: for tempdir in dirs:
if tempdir.startswith('KCC-'): if tempdir.startswith('KCC-'):
@@ -1216,7 +1190,7 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog):
self.ui = QtWidgets.QDialog() self.ui = QtWidgets.QDialog()
self.parser = None self.parser = None
self.setupUi(self.ui) self.setupUi(self.ui)
self.ui.setWindowFlags(self.ui.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint) self.ui.setWindowFlags(self.ui.windowFlags() & ~QtCore.Qt.WindowType.WindowContextHelpButtonHint)
self.okButton.clicked.connect(self.saveData) self.okButton.clicked.connect(self.saveData)
self.cancelButton.clicked.connect(self.ui.close) self.cancelButton.clicked.connect(self.ui.close)
if sys.platform.startswith('linux'): if sys.platform.startswith('linux'):

File diff suppressed because it is too large Load Diff

View File

@@ -1,315 +1,454 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'gui/KCC.ui' ################################################################################
# ## Form generated from reading UI file 'KCC.ui'
# Created by: PyQt5 UI code generator 5.15.6 ##
# ## Created by: Qt User Interface Compiler version 6.5.1
# WARNING: Any manual changes made to this file will be lost when pyuic5 is ##
# run again. Do not edit this file unless you know what you are doing. ## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PyQt5 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QComboBox,
QGridLayout, QHBoxLayout, QLabel, QListWidget,
QListWidgetItem, QMainWindow, QProgressBar, QPushButton,
QSizePolicy, QSlider, QSpinBox, QStatusBar,
QWidget)
from . import KCC_rc
class Ui_mainWindow(object): class Ui_mainWindow(object):
def setupUi(self, mainWindow): def setupUi(self, mainWindow):
mainWindow.setObjectName("mainWindow") if not mainWindow.objectName():
mainWindow.setObjectName(u"mainWindow")
mainWindow.resize(450, 400) mainWindow.resize(450, 400)
icon = QtGui.QIcon() icon = QIcon()
icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Normal, QIcon.Off)
mainWindow.setWindowIcon(icon) mainWindow.setWindowIcon(icon)
self.centralWidget = QtWidgets.QWidget(mainWindow) self.centralWidget = QWidget(mainWindow)
self.centralWidget.setObjectName("centralWidget") self.centralWidget.setObjectName(u"centralWidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralWidget) self.gridLayout = QGridLayout(self.centralWidget)
self.gridLayout.setObjectName(u"gridLayout")
self.gridLayout.setContentsMargins(-1, -1, -1, 5) self.gridLayout.setContentsMargins(-1, -1, -1, 5)
self.gridLayout.setObjectName("gridLayout") self.optionWidget = QWidget(self.centralWidget)
self.optionWidget = QtWidgets.QWidget(self.centralWidget) self.optionWidget.setObjectName(u"optionWidget")
self.optionWidget.setObjectName("optionWidget") self.gridLayout_2 = QGridLayout(self.optionWidget)
self.gridLayout_2 = QtWidgets.QGridLayout(self.optionWidget) self.gridLayout_2.setObjectName(u"gridLayout_2")
self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.gridLayout_2.setObjectName("gridLayout_2") self.upscaleBox = QCheckBox(self.optionWidget)
self.upscaleBox = QtWidgets.QCheckBox(self.optionWidget) self.upscaleBox.setObjectName(u"upscaleBox")
self.upscaleBox.setTristate(True) self.upscaleBox.setTristate(True)
self.upscaleBox.setObjectName("upscaleBox")
self.gridLayout_2.addWidget(self.upscaleBox, 1, 1, 1, 1) self.gridLayout_2.addWidget(self.upscaleBox, 1, 1, 1, 1)
self.rotateBox = QtWidgets.QCheckBox(self.optionWidget)
self.rotateBox = QCheckBox(self.optionWidget)
self.rotateBox.setObjectName(u"rotateBox")
self.rotateBox.setTristate(True) self.rotateBox.setTristate(True)
self.rotateBox.setObjectName("rotateBox")
self.gridLayout_2.addWidget(self.rotateBox, 0, 1, 1, 1) self.gridLayout_2.addWidget(self.rotateBox, 0, 1, 1, 1)
self.outputSplit = QtWidgets.QCheckBox(self.optionWidget)
self.outputSplit.setObjectName("outputSplit") self.outputSplit = QCheckBox(self.optionWidget)
self.outputSplit.setObjectName(u"outputSplit")
self.gridLayout_2.addWidget(self.outputSplit, 2, 1, 1, 1) self.gridLayout_2.addWidget(self.outputSplit, 2, 1, 1, 1)
self.webtoonBox = QtWidgets.QCheckBox(self.optionWidget)
self.webtoonBox.setObjectName("webtoonBox") self.webtoonBox = QCheckBox(self.optionWidget)
self.webtoonBox.setObjectName(u"webtoonBox")
self.gridLayout_2.addWidget(self.webtoonBox, 1, 0, 1, 1) self.gridLayout_2.addWidget(self.webtoonBox, 1, 0, 1, 1)
self.colorBox = QtWidgets.QCheckBox(self.optionWidget)
self.colorBox.setObjectName("colorBox") self.colorBox = QCheckBox(self.optionWidget)
self.colorBox.setObjectName(u"colorBox")
self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1) self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1)
self.gammaBox = QtWidgets.QCheckBox(self.optionWidget)
self.gammaBox.setObjectName("gammaBox") self.gammaBox = QCheckBox(self.optionWidget)
self.gammaBox.setObjectName(u"gammaBox")
self.gridLayout_2.addWidget(self.gammaBox, 1, 2, 1, 1) self.gridLayout_2.addWidget(self.gammaBox, 1, 2, 1, 1)
self.borderBox = QtWidgets.QCheckBox(self.optionWidget)
self.borderBox = QCheckBox(self.optionWidget)
self.borderBox.setObjectName(u"borderBox")
self.borderBox.setTristate(True) self.borderBox.setTristate(True)
self.borderBox.setObjectName("borderBox")
self.gridLayout_2.addWidget(self.borderBox, 2, 0, 1, 1) self.gridLayout_2.addWidget(self.borderBox, 2, 0, 1, 1)
self.mangaBox = QtWidgets.QCheckBox(self.optionWidget)
self.mangaBox.setObjectName("mangaBox") self.mangaBox = QCheckBox(self.optionWidget)
self.mangaBox.setObjectName(u"mangaBox")
self.gridLayout_2.addWidget(self.mangaBox, 0, 0, 1, 1) self.gridLayout_2.addWidget(self.mangaBox, 0, 0, 1, 1)
self.qualityBox = QtWidgets.QCheckBox(self.optionWidget)
self.qualityBox = QCheckBox(self.optionWidget)
self.qualityBox.setObjectName(u"qualityBox")
self.qualityBox.setTristate(True) self.qualityBox.setTristate(True)
self.qualityBox.setObjectName("qualityBox")
self.gridLayout_2.addWidget(self.qualityBox, 0, 2, 1, 1) self.gridLayout_2.addWidget(self.qualityBox, 0, 2, 1, 1)
self.mozJpegBox = QtWidgets.QCheckBox(self.optionWidget)
self.mozJpegBox = QCheckBox(self.optionWidget)
self.mozJpegBox.setObjectName(u"mozJpegBox")
self.mozJpegBox.setTristate(True) self.mozJpegBox.setTristate(True)
self.mozJpegBox.setObjectName("mozJpegBox")
self.gridLayout_2.addWidget(self.mozJpegBox, 3, 0, 1, 1) self.gridLayout_2.addWidget(self.mozJpegBox, 3, 0, 1, 1)
self.maximizeStrips = QtWidgets.QCheckBox(self.optionWidget)
self.maximizeStrips.setObjectName("maximizeStrips") self.maximizeStrips = QCheckBox(self.optionWidget)
self.maximizeStrips.setObjectName(u"maximizeStrips")
self.gridLayout_2.addWidget(self.maximizeStrips, 3, 1, 1, 1) self.gridLayout_2.addWidget(self.maximizeStrips, 3, 1, 1, 1)
self.croppingBox = QtWidgets.QCheckBox(self.optionWidget)
self.croppingBox = QCheckBox(self.optionWidget)
self.croppingBox.setObjectName(u"croppingBox")
self.croppingBox.setTristate(True) self.croppingBox.setTristate(True)
self.croppingBox.setObjectName("croppingBox")
self.gridLayout_2.addWidget(self.croppingBox, 3, 2, 1, 1) self.gridLayout_2.addWidget(self.croppingBox, 3, 2, 1, 1)
self.disableProcessingBox = QtWidgets.QCheckBox(self.optionWidget)
self.disableProcessingBox.setObjectName("disableProcessingBox") self.deleteBox = QCheckBox(self.optionWidget)
self.deleteBox.setObjectName(u"deleteBox")
self.gridLayout_2.addWidget(self.deleteBox, 4, 1, 1, 1)
self.disableProcessingBox = QCheckBox(self.optionWidget)
self.disableProcessingBox.setObjectName(u"disableProcessingBox")
self.gridLayout_2.addWidget(self.disableProcessingBox, 4, 2, 1, 1) self.gridLayout_2.addWidget(self.disableProcessingBox, 4, 2, 1, 1)
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
self.gammaWidget = QtWidgets.QWidget(self.centralWidget)
self.gammaWidget = QWidget(self.centralWidget)
self.gammaWidget.setObjectName(u"gammaWidget")
self.gammaWidget.setVisible(False) self.gammaWidget.setVisible(False)
self.gammaWidget.setObjectName("gammaWidget") self.horizontalLayout_2 = QHBoxLayout(self.gammaWidget)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.gammaWidget) self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.gammaLabel = QLabel(self.gammaWidget)
self.gammaLabel = QtWidgets.QLabel(self.gammaWidget) self.gammaLabel.setObjectName(u"gammaLabel")
self.gammaLabel.setObjectName("gammaLabel")
self.horizontalLayout_2.addWidget(self.gammaLabel) self.horizontalLayout_2.addWidget(self.gammaLabel)
self.gammaSlider = QtWidgets.QSlider(self.gammaWidget)
self.gammaSlider = QSlider(self.gammaWidget)
self.gammaSlider.setObjectName(u"gammaSlider")
self.gammaSlider.setMaximum(250) self.gammaSlider.setMaximum(250)
self.gammaSlider.setSingleStep(5) self.gammaSlider.setSingleStep(5)
self.gammaSlider.setOrientation(QtCore.Qt.Horizontal) self.gammaSlider.setOrientation(Qt.Horizontal)
self.gammaSlider.setObjectName("gammaSlider")
self.horizontalLayout_2.addWidget(self.gammaSlider) self.horizontalLayout_2.addWidget(self.gammaSlider)
self.gridLayout.addWidget(self.gammaWidget, 6, 0, 1, 2) self.gridLayout.addWidget(self.gammaWidget, 6, 0, 1, 2)
self.croppingWidget = QtWidgets.QWidget(self.centralWidget)
self.croppingWidget = QWidget(self.centralWidget)
self.croppingWidget.setObjectName(u"croppingWidget")
self.croppingWidget.setVisible(False) self.croppingWidget.setVisible(False)
self.croppingWidget.setObjectName("croppingWidget") self.horizontalLayout_3 = QHBoxLayout(self.croppingWidget)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.croppingWidget) self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.croppingPowerLabel = QLabel(self.croppingWidget)
self.croppingPowerLabel = QtWidgets.QLabel(self.croppingWidget) self.croppingPowerLabel.setObjectName(u"croppingPowerLabel")
self.croppingPowerLabel.setObjectName("croppingPowerLabel")
self.horizontalLayout_3.addWidget(self.croppingPowerLabel) self.horizontalLayout_3.addWidget(self.croppingPowerLabel)
self.croppingPowerSlider = QtWidgets.QSlider(self.croppingWidget)
self.croppingPowerSlider = QSlider(self.croppingWidget)
self.croppingPowerSlider.setObjectName(u"croppingPowerSlider")
self.croppingPowerSlider.setMaximum(200) self.croppingPowerSlider.setMaximum(200)
self.croppingPowerSlider.setSingleStep(1) self.croppingPowerSlider.setSingleStep(1)
self.croppingPowerSlider.setOrientation(QtCore.Qt.Horizontal) self.croppingPowerSlider.setOrientation(Qt.Horizontal)
self.croppingPowerSlider.setObjectName("croppingPowerSlider")
self.horizontalLayout_3.addWidget(self.croppingPowerSlider) self.horizontalLayout_3.addWidget(self.croppingPowerSlider)
self.gridLayout.addWidget(self.croppingWidget, 8, 0, 1, 2) self.gridLayout.addWidget(self.croppingWidget, 8, 0, 1, 2)
self.buttonWidget = QtWidgets.QWidget(self.centralWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) self.buttonWidget = QWidget(self.centralWidget)
self.buttonWidget.setObjectName(u"buttonWidget")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
self.buttonWidget.setSizePolicy(sizePolicy) self.buttonWidget.setSizePolicy(sizePolicy)
self.buttonWidget.setObjectName("buttonWidget") self.gridLayout_4 = QGridLayout(self.buttonWidget)
self.gridLayout_4 = QtWidgets.QGridLayout(self.buttonWidget) self.gridLayout_4.setObjectName(u"gridLayout_4")
self.gridLayout_4.setContentsMargins(0, 0, 0, 0) self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
self.gridLayout_4.setObjectName("gridLayout_4") self.directoryButton = QPushButton(self.buttonWidget)
self.directoryButton = QtWidgets.QPushButton(self.buttonWidget) self.directoryButton.setObjectName(u"directoryButton")
self.directoryButton.setMinimumSize(QtCore.QSize(0, 30)) self.directoryButton.setMinimumSize(QSize(0, 30))
icon1 = QtGui.QIcon() icon1 = QIcon()
icon1.addPixmap(QtGui.QPixmap(":/Other/icons/folder_new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon1.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Normal, QIcon.Off)
self.directoryButton.setIcon(icon1) self.directoryButton.setIcon(icon1)
self.directoryButton.setObjectName("directoryButton")
self.gridLayout_4.addWidget(self.directoryButton, 0, 0, 1, 1) self.gridLayout_4.addWidget(self.directoryButton, 0, 0, 1, 1)
self.fileButton = QtWidgets.QPushButton(self.buttonWidget)
self.fileButton.setMinimumSize(QtCore.QSize(0, 30)) self.fileButton = QPushButton(self.buttonWidget)
icon2 = QtGui.QIcon() self.fileButton.setObjectName(u"fileButton")
icon2.addPixmap(QtGui.QPixmap(":/Other/icons/document_new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.fileButton.setMinimumSize(QSize(0, 30))
icon2 = QIcon()
icon2.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Normal, QIcon.Off)
self.fileButton.setIcon(icon2) self.fileButton.setIcon(icon2)
self.fileButton.setObjectName("fileButton")
self.gridLayout_4.addWidget(self.fileButton, 0, 3, 1, 1) self.gridLayout_4.addWidget(self.fileButton, 0, 3, 1, 1)
self.deviceBox = QtWidgets.QComboBox(self.buttonWidget)
self.deviceBox.setMinimumSize(QtCore.QSize(0, 28)) self.deviceBox = QComboBox(self.buttonWidget)
self.deviceBox.setObjectName("deviceBox") self.deviceBox.setObjectName(u"deviceBox")
self.deviceBox.setMinimumSize(QSize(0, 28))
self.gridLayout_4.addWidget(self.deviceBox, 1, 0, 1, 1) self.gridLayout_4.addWidget(self.deviceBox, 1, 0, 1, 1)
self.formatBox = QtWidgets.QComboBox(self.buttonWidget)
self.formatBox.setMinimumSize(QtCore.QSize(0, 28)) self.formatBox = QComboBox(self.buttonWidget)
self.formatBox.setObjectName("formatBox") self.formatBox.setObjectName(u"formatBox")
self.formatBox.setMinimumSize(QSize(0, 28))
self.gridLayout_4.addWidget(self.formatBox, 1, 3, 1, 1) self.gridLayout_4.addWidget(self.formatBox, 1, 3, 1, 1)
self.convertButton = QtWidgets.QPushButton(self.buttonWidget)
self.convertButton.setMinimumSize(QtCore.QSize(0, 30)) self.convertButton = QPushButton(self.buttonWidget)
font = QtGui.QFont() self.convertButton.setObjectName(u"convertButton")
self.convertButton.setMinimumSize(QSize(0, 30))
font = QFont()
font.setBold(True) font.setBold(True)
self.convertButton.setFont(font) self.convertButton.setFont(font)
icon3 = QtGui.QIcon() icon3 = QIcon()
icon3.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon3.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Normal, QIcon.Off)
self.convertButton.setIcon(icon3) self.convertButton.setIcon(icon3)
self.convertButton.setObjectName("convertButton")
self.gridLayout_4.addWidget(self.convertButton, 1, 2, 1, 1) self.gridLayout_4.addWidget(self.convertButton, 1, 2, 1, 1)
self.clearButton = QtWidgets.QPushButton(self.buttonWidget)
self.clearButton.setMinimumSize(QtCore.QSize(0, 30)) self.clearButton = QPushButton(self.buttonWidget)
icon4 = QtGui.QIcon() self.clearButton.setObjectName(u"clearButton")
icon4.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.clearButton.setMinimumSize(QSize(0, 30))
icon4 = QIcon()
icon4.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Normal, QIcon.Off)
self.clearButton.setIcon(icon4) self.clearButton.setIcon(icon4)
self.clearButton.setObjectName("clearButton")
self.gridLayout_4.addWidget(self.clearButton, 0, 2, 1, 1) self.gridLayout_4.addWidget(self.clearButton, 0, 2, 1, 1)
self.directoryButton.raise_() self.directoryButton.raise_()
self.clearButton.raise_() self.clearButton.raise_()
self.fileButton.raise_() self.fileButton.raise_()
self.deviceBox.raise_() self.deviceBox.raise_()
self.convertButton.raise_() self.convertButton.raise_()
self.formatBox.raise_() self.formatBox.raise_()
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2) self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
self.toolWidget = QtWidgets.QWidget(self.centralWidget)
self.toolWidget.setObjectName("toolWidget") self.toolWidget = QWidget(self.centralWidget)
self.horizontalLayout = QtWidgets.QHBoxLayout(self.toolWidget) self.toolWidget.setObjectName(u"toolWidget")
self.horizontalLayout = QHBoxLayout(self.toolWidget)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout") self.editorButton = QPushButton(self.toolWidget)
self.editorButton = QtWidgets.QPushButton(self.toolWidget) self.editorButton.setObjectName(u"editorButton")
self.editorButton.setMinimumSize(QtCore.QSize(0, 30)) self.editorButton.setMinimumSize(QSize(0, 30))
icon5 = QtGui.QIcon() icon5 = QIcon()
icon5.addPixmap(QtGui.QPixmap(":/Other/icons/editor.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon5.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Normal, QIcon.Off)
self.editorButton.setIcon(icon5) self.editorButton.setIcon(icon5)
self.editorButton.setObjectName("editorButton")
self.horizontalLayout.addWidget(self.editorButton) self.horizontalLayout.addWidget(self.editorButton)
self.wikiButton = QtWidgets.QPushButton(self.toolWidget)
self.wikiButton.setMinimumSize(QtCore.QSize(0, 30)) self.wikiButton = QPushButton(self.toolWidget)
icon6 = QtGui.QIcon() self.wikiButton.setObjectName(u"wikiButton")
icon6.addPixmap(QtGui.QPixmap(":/Other/icons/wiki.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.wikiButton.setMinimumSize(QSize(0, 30))
icon6 = QIcon()
icon6.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Normal, QIcon.Off)
self.wikiButton.setIcon(icon6) self.wikiButton.setIcon(icon6)
self.wikiButton.setObjectName("wikiButton")
self.horizontalLayout.addWidget(self.wikiButton) self.horizontalLayout.addWidget(self.wikiButton)
self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2) self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2)
self.jobList = QtWidgets.QListWidget(self.centralWidget)
self.jobList.setStyleSheet("QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}") self.jobList = QListWidget(self.centralWidget)
self.jobList.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) self.jobList.setObjectName(u"jobList")
self.jobList.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.jobList.setStyleSheet(u"QListWidget#jobList {background:#ffffff;background-image:url(:/Other/icons/list_background.png);background-position:center center;background-repeat:no-repeat;color:rgb(0,0,0);}")
self.jobList.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.jobList.setSelectionMode(QAbstractItemView.NoSelection)
self.jobList.setObjectName("jobList") self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2) self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
self.progressBar = QtWidgets.QProgressBar(self.centralWidget)
self.progressBar.setMinimumSize(QtCore.QSize(0, 30)) self.progressBar = QProgressBar(self.centralWidget)
font = QtGui.QFont() self.progressBar.setObjectName(u"progressBar")
font.setBold(True) self.progressBar.setMinimumSize(QSize(0, 30))
self.progressBar.setFont(font) self.progressBar.setFont(font)
self.progressBar.setVisible(False) self.progressBar.setVisible(False)
self.progressBar.setAlignment(QtCore.Qt.AlignJustify|QtCore.Qt.AlignVCenter) self.progressBar.setAlignment(Qt.AlignJustify|Qt.AlignVCenter)
self.progressBar.setObjectName("progressBar")
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2) self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
self.customWidget = QtWidgets.QWidget(self.centralWidget)
self.customWidget = QWidget(self.centralWidget)
self.customWidget.setObjectName(u"customWidget")
self.customWidget.setVisible(False) self.customWidget.setVisible(False)
self.customWidget.setObjectName("customWidget") self.gridLayout_3 = QGridLayout(self.customWidget)
self.gridLayout_3 = QtWidgets.QGridLayout(self.customWidget) self.gridLayout_3.setObjectName(u"gridLayout_3")
self.gridLayout_3.setContentsMargins(0, 0, 0, 0) self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
self.gridLayout_3.setObjectName("gridLayout_3") self.hLabel = QLabel(self.customWidget)
self.hLabel = QtWidgets.QLabel(self.customWidget) self.hLabel.setObjectName(u"hLabel")
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy1 = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0) sizePolicy1.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy1.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth()) sizePolicy1.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
self.hLabel.setSizePolicy(sizePolicy) self.hLabel.setSizePolicy(sizePolicy1)
self.hLabel.setObjectName("hLabel")
self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1) self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
self.widthBox = QtWidgets.QSpinBox(self.customWidget)
self.widthBox = QSpinBox(self.customWidget)
self.widthBox.setObjectName(u"widthBox")
self.widthBox.setMaximum(2160) self.widthBox.setMaximum(2160)
self.widthBox.setObjectName("widthBox")
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1) self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
self.wLabel = QtWidgets.QLabel(self.customWidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) self.wLabel = QLabel(self.customWidget)
sizePolicy.setHorizontalStretch(0) self.wLabel.setObjectName(u"wLabel")
sizePolicy.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
sizePolicy.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth()) self.wLabel.setSizePolicy(sizePolicy1)
self.wLabel.setSizePolicy(sizePolicy)
self.wLabel.setObjectName("wLabel")
self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1) self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
self.heightBox = QtWidgets.QSpinBox(self.customWidget)
self.heightBox = QSpinBox(self.customWidget)
self.heightBox.setObjectName(u"heightBox")
self.heightBox.setMaximum(3840) self.heightBox.setMaximum(3840)
self.heightBox.setObjectName("heightBox")
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1) self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
self.gridLayout.addWidget(self.customWidget, 7, 0, 1, 2) self.gridLayout.addWidget(self.customWidget, 7, 0, 1, 2)
mainWindow.setCentralWidget(self.centralWidget) mainWindow.setCentralWidget(self.centralWidget)
self.statusBar = QtWidgets.QStatusBar(mainWindow) self.statusBar = QStatusBar(mainWindow)
self.statusBar.setObjectName(u"statusBar")
self.statusBar.setSizeGripEnabled(False) self.statusBar.setSizeGripEnabled(False)
self.statusBar.setObjectName("statusBar")
mainWindow.setStatusBar(self.statusBar) mainWindow.setStatusBar(self.statusBar)
QWidget.setTabOrder(self.convertButton, self.clearButton)
QWidget.setTabOrder(self.clearButton, self.directoryButton)
QWidget.setTabOrder(self.directoryButton, self.fileButton)
QWidget.setTabOrder(self.fileButton, self.deviceBox)
QWidget.setTabOrder(self.deviceBox, self.formatBox)
QWidget.setTabOrder(self.formatBox, self.mangaBox)
QWidget.setTabOrder(self.mangaBox, self.rotateBox)
QWidget.setTabOrder(self.rotateBox, self.qualityBox)
QWidget.setTabOrder(self.qualityBox, self.webtoonBox)
QWidget.setTabOrder(self.webtoonBox, self.upscaleBox)
QWidget.setTabOrder(self.upscaleBox, self.gammaBox)
QWidget.setTabOrder(self.gammaBox, self.borderBox)
QWidget.setTabOrder(self.borderBox, self.outputSplit)
QWidget.setTabOrder(self.outputSplit, self.colorBox)
QWidget.setTabOrder(self.colorBox, self.croppingBox)
QWidget.setTabOrder(self.croppingBox, self.mozJpegBox)
QWidget.setTabOrder(self.mozJpegBox, self.maximizeStrips)
QWidget.setTabOrder(self.maximizeStrips, self.deleteBox)
QWidget.setTabOrder(self.deleteBox, self.disableProcessingBox)
QWidget.setTabOrder(self.disableProcessingBox, self.editorButton)
QWidget.setTabOrder(self.editorButton, self.wikiButton)
QWidget.setTabOrder(self.wikiButton, self.jobList)
QWidget.setTabOrder(self.jobList, self.gammaSlider)
QWidget.setTabOrder(self.gammaSlider, self.widthBox)
QWidget.setTabOrder(self.widthBox, self.heightBox)
QWidget.setTabOrder(self.heightBox, self.croppingPowerSlider)
self.retranslateUi(mainWindow) self.retranslateUi(mainWindow)
QtCore.QMetaObject.connectSlotsByName(mainWindow)
mainWindow.setTabOrder(self.convertButton, self.clearButton) QMetaObject.connectSlotsByName(mainWindow)
mainWindow.setTabOrder(self.clearButton, self.directoryButton) # setupUi
mainWindow.setTabOrder(self.directoryButton, self.fileButton)
mainWindow.setTabOrder(self.fileButton, self.deviceBox)
mainWindow.setTabOrder(self.deviceBox, self.formatBox)
mainWindow.setTabOrder(self.formatBox, self.mangaBox)
mainWindow.setTabOrder(self.mangaBox, self.rotateBox)
mainWindow.setTabOrder(self.rotateBox, self.qualityBox)
mainWindow.setTabOrder(self.qualityBox, self.webtoonBox)
mainWindow.setTabOrder(self.webtoonBox, self.upscaleBox)
mainWindow.setTabOrder(self.upscaleBox, self.gammaBox)
mainWindow.setTabOrder(self.gammaBox, self.borderBox)
mainWindow.setTabOrder(self.borderBox, self.outputSplit)
mainWindow.setTabOrder(self.outputSplit, self.colorBox)
mainWindow.setTabOrder(self.colorBox, self.croppingBox)
mainWindow.setTabOrder(self.croppingBox, self.mozJpegBox)
mainWindow.setTabOrder(self.mozJpegBox, self.maximizeStrips)
mainWindow.setTabOrder(self.maximizeStrips, self.disableProcessingBox)
mainWindow.setTabOrder(self.disableProcessingBox, self.editorButton)
mainWindow.setTabOrder(self.editorButton, self.wikiButton)
mainWindow.setTabOrder(self.wikiButton, self.jobList)
mainWindow.setTabOrder(self.jobList, self.gammaSlider)
mainWindow.setTabOrder(self.gammaSlider, self.widthBox)
mainWindow.setTabOrder(self.widthBox, self.heightBox)
mainWindow.setTabOrder(self.heightBox, self.croppingPowerSlider)
def retranslateUi(self, mainWindow): def retranslateUi(self, mainWindow):
_translate = QtCore.QCoreApplication.translate mainWindow.setWindowTitle(QCoreApplication.translate("mainWindow", u"Kindle Comic Converter", None))
mainWindow.setWindowTitle(_translate("mainWindow", "Kindle Comic Converter")) #if QT_CONFIG(tooltip)
self.upscaleBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>")) self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>", None))
self.upscaleBox.setText(_translate("mainWindow", "Stretch/Upscale")) #endif // QT_CONFIG(tooltip)
self.rotateBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>")) self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None))
self.rotateBox.setText(_translate("mainWindow", "Spread splitter")) #if QT_CONFIG(tooltip)
self.outputSplit.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>")) self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>", None))
self.outputSplit.setText(_translate("mainWindow", "Output split")) #endif // QT_CONFIG(tooltip)
self.webtoonBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for Korean Webtoons.</p></body></html>")) self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None))
self.webtoonBox.setText(_translate("mainWindow", "Webtoon mode")) #if QT_CONFIG(tooltip)
self.colorBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable conversion to grayscale.</p></body></html>")) self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>", None))
self.colorBox.setText(_translate("mainWindow", "Color mode")) #endif // QT_CONFIG(tooltip)
self.gammaBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable automatic gamma correction.</p></body></html>")) self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
self.gammaBox.setText(_translate("mainWindow", "Custom gamma")) #if QT_CONFIG(tooltip)
self.borderBox.setToolTip(_translate("mainWindow", "<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>")) self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>", None))
self.borderBox.setText(_translate("mainWindow", "W/B margins")) #endif // QT_CONFIG(tooltip)
self.mangaBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable right-to-left reading.</p></body></html>")) self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
self.mangaBox.setText(_translate("mainWindow", "Manga mode")) #if QT_CONFIG(tooltip)
self.qualityBox.setToolTip(_translate("mainWindow", "<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>")) self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html>", None))
self.qualityBox.setText(_translate("mainWindow", "Panel View 4/2/HQ")) #endif // QT_CONFIG(tooltip)
self.mozJpegBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html>")) self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None))
self.mozJpegBox.setText(_translate("mainWindow", "JPEG/PNG/mozJpeg")) #if QT_CONFIG(tooltip)
self.maximizeStrips.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html>")) self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Disable automatic gamma correction.</p></body></html>", None))
self.maximizeStrips.setText(_translate("mainWindow", "1x4 to 2x2 strips")) #endif // QT_CONFIG(tooltip)
self.croppingBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Margins<br/></span>Margins</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html>")) self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None))
self.croppingBox.setText(_translate("mainWindow", "Cropping mode")) #if QT_CONFIG(tooltip)
self.disableProcessingBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Do not process any image, ignore profil and processing options</p></body></html>")) 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.disableProcessingBox.setText(_translate("mainWindow", "Disable processing")) #endif // QT_CONFIG(tooltip)
self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto")) self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None))
self.croppingPowerLabel.setText(_translate("mainWindow", "Cropping power:")) #if QT_CONFIG(tooltip)
self.directoryButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>")) self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>", None))
self.directoryButton.setText(_translate("mainWindow", "Add directory")) #endif // QT_CONFIG(tooltip)
self.fileButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>")) self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Manga mode", None))
self.fileButton.setText(_translate("mainWindow", "Add file")) #if QT_CONFIG(tooltip)
self.deviceBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Target device.</p></body></html>")) 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))
self.formatBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Output format.</p></body></html>")) #endif // QT_CONFIG(tooltip)
self.convertButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to select the output directory.</p></body></html>")) self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None))
self.convertButton.setText(_translate("mainWindow", "Convert")) #if QT_CONFIG(tooltip)
self.clearButton.setText(_translate("mainWindow", "Clear list")) self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html>", None))
self.editorButton.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Shift+Click to edit directory.</p></body></html>")) #endif // QT_CONFIG(tooltip)
self.editorButton.setText(_translate("mainWindow", "Editor")) self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None))
self.wikiButton.setText(_translate("mainWindow", "Wiki")) #if QT_CONFIG(tooltip)
self.hLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>")) self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html>", None))
self.hLabel.setText(_translate("mainWindow", "Custom height:")) #endif // QT_CONFIG(tooltip)
self.widthBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>")) self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
self.wLabel.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>")) #if QT_CONFIG(tooltip)
self.wLabel.setText(_translate("mainWindow", "Custom width:")) self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Margins<br/></span>Margins</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html>", None))
self.heightBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Resolution of the target device.</p></body></html>")) #endif // QT_CONFIG(tooltip)
from . import KCC_rc self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
#if QT_CONFIG(tooltip)
self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None))
#endif // QT_CONFIG(tooltip)
self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None))
#if QT_CONFIG(tooltip)
self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><pre style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Do not process any image, ignore profile and processing options</pre></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None))
self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None))
#if QT_CONFIG(tooltip)
self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add directory", None))
#if QT_CONFIG(tooltip)
self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add file", None))
#if QT_CONFIG(tooltip)
self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.convertButton.setText(QCoreApplication.translate("mainWindow", u"Convert", None))
self.clearButton.setText(QCoreApplication.translate("mainWindow", u"Clear list", None))
#if QT_CONFIG(tooltip)
self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.editorButton.setText(QCoreApplication.translate("mainWindow", u"Editor", None))
self.wikiButton.setText(QCoreApplication.translate("mainWindow", u"Wiki", None))
#if QT_CONFIG(tooltip)
self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.hLabel.setText(QCoreApplication.translate("mainWindow", u"Custom height:", None))
#if QT_CONFIG(tooltip)
self.widthBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.wLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.wLabel.setText(QCoreApplication.translate("mainWindow", u"Custom width:", None))
#if QT_CONFIG(tooltip)
self.heightBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
# retranslateUi

View File

@@ -1,118 +1,168 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'gui/MetaEditor.ui' ################################################################################
# ## Form generated from reading UI file 'MetaEditor.ui'
# Created by: PyQt5 UI code generator 5.15.2 ##
# ## Created by: Qt User Interface Compiler version 6.5.1
# WARNING: Any manual changes made to this file will be lost when pyuic5 is ##
# run again. Do not edit this file unless you know what you are doing. ## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PyQt5 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QDialog, QGridLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QSizePolicy,
QVBoxLayout, QWidget)
from . import KCC_rc
class Ui_editorDialog(object): class Ui_editorDialog(object):
def setupUi(self, editorDialog): def setupUi(self, editorDialog):
editorDialog.setObjectName("editorDialog") if not editorDialog.objectName():
editorDialog.setObjectName(u"editorDialog")
editorDialog.resize(400, 260) editorDialog.resize(400, 260)
editorDialog.setMinimumSize(QtCore.QSize(400, 260)) editorDialog.setMinimumSize(QSize(400, 260))
icon = QtGui.QIcon() icon = QIcon()
icon.addPixmap(QtGui.QPixmap(":/Icon/icons/comic2ebook.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Normal, QIcon.Off)
editorDialog.setWindowIcon(icon) editorDialog.setWindowIcon(icon)
self.verticalLayout = QtWidgets.QVBoxLayout(editorDialog) self.verticalLayout = QVBoxLayout(editorDialog)
self.verticalLayout.setObjectName(u"verticalLayout")
self.verticalLayout.setContentsMargins(-1, -1, -1, 5) self.verticalLayout.setContentsMargins(-1, -1, -1, 5)
self.verticalLayout.setObjectName("verticalLayout") self.editorWidget = QWidget(editorDialog)
self.editorWidget = QtWidgets.QWidget(editorDialog) self.editorWidget.setObjectName(u"editorWidget")
self.editorWidget.setObjectName("editorWidget") self.gridLayout = QGridLayout(self.editorWidget)
self.gridLayout = QtWidgets.QGridLayout(self.editorWidget) self.gridLayout.setObjectName(u"gridLayout")
self.gridLayout.setContentsMargins(0, 0, 0, 0) self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout") self.label_1 = QLabel(self.editorWidget)
self.label_1 = QtWidgets.QLabel(self.editorWidget) self.label_1.setObjectName(u"label_1")
self.label_1.setObjectName("label_1")
self.gridLayout.addWidget(self.label_1, 0, 0, 1, 1) self.gridLayout.addWidget(self.label_1, 0, 0, 1, 1)
self.seriesLine = QtWidgets.QLineEdit(self.editorWidget)
self.seriesLine.setObjectName("seriesLine") self.seriesLine = QLineEdit(self.editorWidget)
self.seriesLine.setObjectName(u"seriesLine")
self.gridLayout.addWidget(self.seriesLine, 0, 1, 1, 1) self.gridLayout.addWidget(self.seriesLine, 0, 1, 1, 1)
self.label_2 = QtWidgets.QLabel(self.editorWidget)
self.label_2.setObjectName("label_2") self.label_2 = QLabel(self.editorWidget)
self.label_2.setObjectName(u"label_2")
self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
self.volumeLine = QtWidgets.QLineEdit(self.editorWidget)
self.volumeLine.setObjectName("volumeLine") self.volumeLine = QLineEdit(self.editorWidget)
self.volumeLine.setObjectName(u"volumeLine")
self.gridLayout.addWidget(self.volumeLine, 1, 1, 1, 1) self.gridLayout.addWidget(self.volumeLine, 1, 1, 1, 1)
self.label_3 = QtWidgets.QLabel(self.editorWidget)
self.label_3.setObjectName("label_3") self.label_3 = QLabel(self.editorWidget)
self.label_3.setObjectName(u"label_3")
self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1) self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
self.numberLine = QtWidgets.QLineEdit(self.editorWidget)
self.numberLine.setObjectName("numberLine") self.numberLine = QLineEdit(self.editorWidget)
self.numberLine.setObjectName(u"numberLine")
self.gridLayout.addWidget(self.numberLine, 2, 1, 1, 1) self.gridLayout.addWidget(self.numberLine, 2, 1, 1, 1)
self.label_4 = QtWidgets.QLabel(self.editorWidget)
self.label_4.setObjectName("label_4") self.label_4 = QLabel(self.editorWidget)
self.label_4.setObjectName(u"label_4")
self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1) self.gridLayout.addWidget(self.label_4, 3, 0, 1, 1)
self.writerLine = QtWidgets.QLineEdit(self.editorWidget)
self.writerLine.setObjectName("writerLine") self.writerLine = QLineEdit(self.editorWidget)
self.writerLine.setObjectName(u"writerLine")
self.gridLayout.addWidget(self.writerLine, 3, 1, 1, 1) self.gridLayout.addWidget(self.writerLine, 3, 1, 1, 1)
self.label_5 = QtWidgets.QLabel(self.editorWidget)
self.label_5.setObjectName("label_5") self.label_5 = QLabel(self.editorWidget)
self.label_5.setObjectName(u"label_5")
self.gridLayout.addWidget(self.label_5, 4, 0, 1, 1) self.gridLayout.addWidget(self.label_5, 4, 0, 1, 1)
self.pencillerLine = QtWidgets.QLineEdit(self.editorWidget)
self.pencillerLine.setObjectName("pencillerLine") self.pencillerLine = QLineEdit(self.editorWidget)
self.pencillerLine.setObjectName(u"pencillerLine")
self.gridLayout.addWidget(self.pencillerLine, 4, 1, 1, 1) self.gridLayout.addWidget(self.pencillerLine, 4, 1, 1, 1)
self.label_6 = QtWidgets.QLabel(self.editorWidget)
self.label_6.setObjectName("label_6") self.label_6 = QLabel(self.editorWidget)
self.label_6.setObjectName(u"label_6")
self.gridLayout.addWidget(self.label_6, 5, 0, 1, 1) self.gridLayout.addWidget(self.label_6, 5, 0, 1, 1)
self.inkerLine = QtWidgets.QLineEdit(self.editorWidget)
self.inkerLine.setObjectName("inkerLine") self.inkerLine = QLineEdit(self.editorWidget)
self.inkerLine.setObjectName(u"inkerLine")
self.gridLayout.addWidget(self.inkerLine, 5, 1, 1, 1) self.gridLayout.addWidget(self.inkerLine, 5, 1, 1, 1)
self.label_7 = QtWidgets.QLabel(self.editorWidget)
self.label_7.setObjectName("label_7") self.label_7 = QLabel(self.editorWidget)
self.label_7.setObjectName(u"label_7")
self.gridLayout.addWidget(self.label_7, 6, 0, 1, 1) self.gridLayout.addWidget(self.label_7, 6, 0, 1, 1)
self.coloristLine = QtWidgets.QLineEdit(self.editorWidget)
self.coloristLine.setObjectName("coloristLine") self.coloristLine = QLineEdit(self.editorWidget)
self.coloristLine.setObjectName(u"coloristLine")
self.gridLayout.addWidget(self.coloristLine, 6, 1, 1, 1) self.gridLayout.addWidget(self.coloristLine, 6, 1, 1, 1)
self.verticalLayout.addWidget(self.editorWidget) self.verticalLayout.addWidget(self.editorWidget)
self.optionWidget = QtWidgets.QWidget(editorDialog)
self.optionWidget.setObjectName("optionWidget") self.optionWidget = QWidget(editorDialog)
self.horizontalLayout = QtWidgets.QHBoxLayout(self.optionWidget) self.optionWidget.setObjectName(u"optionWidget")
self.horizontalLayout = QHBoxLayout(self.optionWidget)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout.setContentsMargins(0, 0, 0, 0) self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout") self.statusLabel = QLabel(self.optionWidget)
self.statusLabel = QtWidgets.QLabel(self.optionWidget) self.statusLabel.setObjectName(u"statusLabel")
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.statusLabel.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.statusLabel.sizePolicy().hasHeightForWidth())
self.statusLabel.setSizePolicy(sizePolicy) self.statusLabel.setSizePolicy(sizePolicy)
self.statusLabel.setText("")
self.statusLabel.setObjectName("statusLabel")
self.horizontalLayout.addWidget(self.statusLabel) self.horizontalLayout.addWidget(self.statusLabel)
self.okButton = QtWidgets.QPushButton(self.optionWidget)
self.okButton.setMinimumSize(QtCore.QSize(0, 30)) self.okButton = QPushButton(self.optionWidget)
icon1 = QtGui.QIcon() self.okButton.setObjectName(u"okButton")
icon1.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.okButton.setMinimumSize(QSize(0, 30))
icon1 = QIcon()
icon1.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Normal, QIcon.Off)
self.okButton.setIcon(icon1) self.okButton.setIcon(icon1)
self.okButton.setObjectName("okButton")
self.horizontalLayout.addWidget(self.okButton) self.horizontalLayout.addWidget(self.okButton)
self.cancelButton = QtWidgets.QPushButton(self.optionWidget)
self.cancelButton.setMinimumSize(QtCore.QSize(0, 30)) self.cancelButton = QPushButton(self.optionWidget)
icon2 = QtGui.QIcon() self.cancelButton.setObjectName(u"cancelButton")
icon2.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.cancelButton.setMinimumSize(QSize(0, 30))
icon2 = QIcon()
icon2.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Normal, QIcon.Off)
self.cancelButton.setIcon(icon2) self.cancelButton.setIcon(icon2)
self.cancelButton.setObjectName("cancelButton")
self.horizontalLayout.addWidget(self.cancelButton) self.horizontalLayout.addWidget(self.cancelButton)
self.verticalLayout.addWidget(self.optionWidget) self.verticalLayout.addWidget(self.optionWidget)
self.retranslateUi(editorDialog) self.retranslateUi(editorDialog)
QtCore.QMetaObject.connectSlotsByName(editorDialog)
QMetaObject.connectSlotsByName(editorDialog)
# setupUi
def retranslateUi(self, editorDialog): def retranslateUi(self, editorDialog):
_translate = QtCore.QCoreApplication.translate editorDialog.setWindowTitle(QCoreApplication.translate("editorDialog", u"Metadata editor", None))
editorDialog.setWindowTitle(_translate("editorDialog", "Metadata editor")) self.label_1.setText(QCoreApplication.translate("editorDialog", u"Series:", None))
self.label_1.setText(_translate("editorDialog", "Series:")) self.label_2.setText(QCoreApplication.translate("editorDialog", u"Volume:", None))
self.label_2.setText(_translate("editorDialog", "Volume:")) self.label_3.setText(QCoreApplication.translate("editorDialog", u"Number:", None))
self.label_3.setText(_translate("editorDialog", "Number:")) self.label_4.setText(QCoreApplication.translate("editorDialog", u"Writer:", None))
self.label_4.setText(_translate("editorDialog", "Writer:")) self.label_5.setText(QCoreApplication.translate("editorDialog", u"Penciller:", None))
self.label_5.setText(_translate("editorDialog", "Penciller:")) self.label_6.setText(QCoreApplication.translate("editorDialog", u"Inker:", None))
self.label_6.setText(_translate("editorDialog", "Inker:")) self.label_7.setText(QCoreApplication.translate("editorDialog", u"Colorist:", None))
self.label_7.setText(_translate("editorDialog", "Colorist:")) self.statusLabel.setText("")
self.okButton.setText(_translate("editorDialog", "Save")) self.okButton.setText(QCoreApplication.translate("editorDialog", u"Save", None))
self.cancelButton.setText(_translate("editorDialog", "Cancel")) self.cancelButton.setText(QCoreApplication.translate("editorDialog", u"Cancel", None))
from . import KCC_rc # retranslateUi

View File

@@ -1,4 +1,4 @@
__version__ = '5.6.0' __version__ = '5.6.3'
__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

@@ -20,6 +20,7 @@
import os import os
import sys import sys
from argparse import ArgumentParser
from time import strftime, gmtime from time import strftime, gmtime
from copy import copy from copy import copy
from glob import glob, escape from glob import glob, escape
@@ -27,19 +28,14 @@ from re import sub
from stat import S_IWRITE, S_IREAD, S_IEXEC from stat import S_IWRITE, S_IREAD, S_IEXEC
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from tempfile import mkdtemp, gettempdir, TemporaryFile from tempfile import mkdtemp, gettempdir, TemporaryFile
from shutil import move, copytree, rmtree from shutil import move, copytree, rmtree, copyfile
from optparse import OptionParser, OptionGroup
from multiprocessing import Pool from multiprocessing import Pool
from uuid import uuid4 from uuid import uuid4
from slugify import slugify as slugifyExt 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 Popen, virtual_memory, disk_usage
from html import escape as hescape from html import escape as hescape
try:
from PyQt5 import QtCore
except ImportError:
QtCore = None
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace
from . import comic2panel from . import comic2panel
from . import image from . import image
@@ -54,23 +50,23 @@ from . import __version__
def main(argv=None): def main(argv=None):
global options global options
parser = makeParser() parser = makeParser()
optionstemplate, args = parser.parse_args(argv) args = parser.parse_args(argv)
if len(args) == 0: options = copy(args)
if not argv or options.input == []:
parser.print_help() parser.print_help()
return 0 return 0
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
sources = set([source for arg in args for source in glob(escape(arg))]) sources = set([source for option in options.input for source in glob(escape(option))])
else: else:
sources = set(args) sources = set(options.input)
if len(sources) == 0: if len(sources) == 0:
print('No matching files found.') print('No matching files found.')
return 1 return 1
for source in sources: for source in sources:
source = source.rstrip('\\').rstrip('/') source = source.rstrip('\\').rstrip('/')
options = copy(optionstemplate) options = copy(args)
options = checkOptions(options) options = checkOptions(options)
if len(sources) > 1: print('Working on ' + source + '...')
print('Working on ' + source + '...')
makeBook(source) makeBook(source)
return 0 return 0
@@ -295,13 +291,19 @@ def buildOPF(dstdir, title, filelist, cover=None):
"<meta name=\"zero-gutter\" content=\"true\"/>\n", "<meta name=\"zero-gutter\" content=\"true\"/>\n",
"<meta name=\"zero-margin\" content=\"true\"/>\n", "<meta name=\"zero-margin\" content=\"true\"/>\n",
"<meta name=\"ke-border-color\" content=\"#FFFFFF\"/>\n", "<meta name=\"ke-border-color\" content=\"#FFFFFF\"/>\n",
"<meta name=\"ke-border-width\" content=\"0\"/>\n"]) "<meta name=\"ke-border-width\" content=\"0\"/>\n",
"<meta property=\"rendition:spread\">landscape</meta>\n",
"<meta property=\"rendition:layout\">pre-paginated</meta>\n",
"<meta name=\"orientation-lock\" content=\"none\"/>\n"])
if options.kfx: if options.kfx:
f.writelines(["<meta name=\"orientation-lock\" content=\"none\"/>\n", f.writelines(["<meta name=\"region-mag\" content=\"false\"/>\n"])
"<meta name=\"region-mag\" content=\"false\"/>\n"])
else: else:
f.writelines(["<meta name=\"orientation-lock\" content=\"portrait\"/>\n", f.writelines(["<meta name=\"region-mag\" content=\"true\"/>\n"])
"<meta name=\"region-mag\" content=\"true\"/>\n"]) elif options.supportSyntheticSpread:
f.writelines([
"<meta property=\"rendition:spread\">landscape</meta>\n",
"<meta property=\"rendition:layout\">pre-paginated</meta>\n"
])
else: else:
f.writelines(["<meta property=\"rendition:orientation\">portrait</meta>\n", f.writelines(["<meta property=\"rendition:orientation\">portrait</meta>\n",
"<meta property=\"rendition:spread\">portrait</meta>\n", "<meta property=\"rendition:spread\">portrait</meta>\n",
@@ -334,38 +336,64 @@ def buildOPF(dstdir, title, filelist, cover=None):
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\"" + f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\"" +
mt + "\"/>\n") mt + "\"/>\n")
f.write("<item id=\"css\" href=\"Text/style.css\" media-type=\"text/css\"/>\n") f.write("<item id=\"css\" href=\"Text/style.css\" media-type=\"text/css\"/>\n")
def pageSpreadProperty(pageside):
if options.iskindle:
return "linear=\"yes\" properties=\"page-spread-%s\"" % pageside
elif options.isKobo:
return "properties=\"rendition:page-spread-%s\"" % pageside
else:
return ""
if options.righttoleft: if options.righttoleft:
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n") f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
pageside = "right" pageside = "right"
else: else:
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n") f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
pageside = "left" pageside = "left"
if options.iskindle: if options.iskindle or options.supportSyntheticSpread:
for entry in reflist: for entry in reflist:
if options.righttoleft: if options.righttoleft:
if entry.endswith("-b"): if entry.endswith("-b"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-right\"/>\n") f.write(
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty("right"))
)
pageside = "right" pageside = "right"
elif entry.endswith("-c"): elif entry.endswith("-c"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-left\"/>\n") f.write(
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty("left"))
)
pageside = "right" pageside = "right"
else: else:
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-" + f.write(
pageside + "\"/>\n") "<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty(pageside))
)
if pageside == "right": if pageside == "right":
pageside = "left" pageside = "left"
else: else:
pageside = "right" pageside = "right"
else: else:
if entry.endswith("-b"): if entry.endswith("-b"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-left\"/>\n") f.write(
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty("left"))
)
pageside = "left" pageside = "left"
elif entry.endswith("-c"): elif entry.endswith("-c"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-right\"/>\n") f.write(
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty("right"))
)
pageside = "left" pageside = "left"
else: else:
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-" + f.write(
pageside + "\"/>\n") "<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty(pageside))
)
if pageside == "right": if pageside == "right":
pageside = "left" pageside = "left"
else: else:
@@ -482,18 +510,30 @@ def buildEPUB(path, chapternames, tomenumber):
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks # Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
if not chapternames and options.chapters: if not chapternames and options.chapters:
chapterlist = [] chapterlist = []
globaldiff = 0
global_diff = 0
diff_delta = 0
# if split
if options.splitter == 0:
diff_delta = 1
# if rotate and split
elif options.splitter == 2:
diff_delta = 2
for aChapter in options.chapters: for aChapter in options.chapters:
pageid = aChapter[0] pageid = aChapter[0]
for x in range(0, pageid + globaldiff + 1): cur_diff = global_diff
global_diff = 0
for x in range(0, pageid + cur_diff + 1):
if '-kcc-b' in filelist[x][1]: if '-kcc-b' in filelist[x][1]:
pageid += 1 pageid += diff_delta
if '-kcc-c' in filelist[pageid][1]: global_diff += diff_delta
pageid -= 1
filename = filelist[pageid][1] filename = filelist[pageid][1]
chapterlist.append((filelist[pageid][0].replace('Images', 'Text'), filename)) chapterlist.append((filelist[pageid][0].replace('Images', 'Text'), filename))
chapternames[filename] = aChapter[1] chapternames[filename] = aChapter[1]
globaldiff = pageid - (aChapter[0] + globaldiff)
buildNCX(path, options.title, chapterlist, chapternames) buildNCX(path, options.title, chapterlist, chapternames)
buildNAV(path, options.title, chapterlist, chapternames) buildNAV(path, options.title, chapterlist, chapternames)
buildOPF(path, options.title, filelist, cover) buildOPF(path, options.title, filelist, cover)
@@ -515,7 +555,7 @@ def imgDirectoryProcessing(path):
GUI.progressBarTick.emit(str(pagenumber)) GUI.progressBarTick.emit(str(pagenumber))
if len(work) > 0: if len(work) > 0:
for i in work: for i in work:
workerPool.apply_async(func=imgFileProcessing, args=(i, ), callback=imgFileProcessingTick) workerPool.apply_async(func=imgFileProcessing, args=(i,), callback=imgFileProcessingTick)
workerPool.close() workerPool.close()
workerPool.join() workerPool.join()
if GUI and not GUI.conversionAlive: if GUI and not GUI.conversionAlive:
@@ -557,9 +597,9 @@ def imgFileProcessing(work):
for i in workImg.payload: for i in workImg.payload:
img = image.ComicPage(opt, *i) img = image.ComicPage(opt, *i)
if opt.cropping == 2 and not opt.webtoon: if opt.cropping == 2 and not opt.webtoon:
img.cropPageNumber(opt.croppingp) img.cropPageNumber(opt.croppingp, opt.croppingm)
if opt.cropping > 0 and not opt.webtoon: if opt.cropping > 0 and not opt.webtoon:
img.cropMargin(opt.croppingp) img.cropMargin(opt.croppingp, opt.croppingm)
img.autocontrastImage() img.autocontrastImage()
img.resizeImage() img.resizeImage()
if opt.forcepng and not opt.forcecolor: if opt.forcepng and not opt.forcecolor:
@@ -600,7 +640,7 @@ def getWorkFolder(afile):
path = cbx.extract(workdir) path = cbx.extract(workdir)
except OSError as e: except OSError as e:
rmtree(workdir, True) rmtree(workdir, True)
raise UserWarning(e.strerror) raise UserWarning(e)
else: else:
raise UserWarning("Failed to open source file/directory.") raise UserWarning("Failed to open source file/directory.")
sanitizePermissions(path) sanitizePermissions(path)
@@ -664,7 +704,9 @@ def getComicInfo(path, originalpath):
os.remove(xmlPath) os.remove(xmlPath)
return return
options.authors = [] options.authors = []
if defaultTitle: if xml.data['Title']:
options.title = hescape(xml.data['Title'])
elif defaultTitle:
if xml.data['Series']: if xml.data['Series']:
options.title = hescape(xml.data['Series']) options.title = hescape(xml.data['Series'])
if xml.data['Volume']: if xml.data['Volume']:
@@ -715,19 +757,23 @@ 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 name in files: for i, name in enumerate(sorted(files)):
splitname = os.path.splitext(name) splitname = os.path.splitext(name)
slugified = slugify(splitname[0], False)
while os.path.exists(os.path.join(root, slugified + splitname[1])) and splitname[0].upper()\ # file needs kcc at front AND back to avoid renaming issues
!= slugified.upper(): slugified = f'kcc-{i:04}'
slugified += "A" for suffix in '-KCC', '-KCC-A', '-KCC-B', '-KCC-C':
if splitname[0].endswith(suffix):
slugified += suffix.lower()
break
newKey = os.path.join(root, slugified + splitname[1]) newKey = os.path.join(root, slugified + splitname[1])
key = os.path.join(root, name) key = os.path.join(root, name)
if key != newKey: if key != newKey:
os.replace(key, newKey) os.replace(key, newKey)
for name in dirs: for name in dirs:
tmpName = name tmpName = name
slugified = slugify(name, True) slugified = slugify(name)
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper(): while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
slugified += "A" slugified += "A"
chapterNames[slugified] = tmpName chapterNames[slugified] = tmpName
@@ -738,23 +784,6 @@ def sanitizeTree(filetree):
return chapterNames return chapterNames
def sanitizeTreeKobo(filetree):
pageNumber = 0
for root, dirs, files in os.walk(filetree):
dirs, files = walkSort(dirs, files)
for name in files:
splitname = os.path.splitext(name)
slugified = str(pageNumber).zfill(5)
pageNumber += 1
while os.path.exists(os.path.join(root, slugified + splitname[1])) and splitname[0].upper()\
!= slugified.upper():
slugified += "A"
newKey = os.path.join(root, slugified + splitname[1])
key = os.path.join(root, name)
if key != newKey:
os.replace(key, newKey)
def sanitizePermissions(filetree): def sanitizePermissions(filetree):
for root, dirs, files in os.walk(filetree, False): for root, dirs, files in os.walk(filetree, False):
for name in files: for name in files:
@@ -877,11 +906,8 @@ def createNewTome():
return tomePath, tomePathRoot return tomePath, tomePathRoot
def slugify(value, isdir): def slugify(value):
if isdir: value = slugify_ext(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
value = slugifyExt(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
else:
value = slugifyExt(value).strip('.')
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2)) value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
return value return value
@@ -902,90 +928,95 @@ def makeZIP(zipfilename, basedir, isepub=False):
def makeParser(): def makeParser():
psr = OptionParser(usage="Usage: kcc-c2e [options] comic_file|comic_folder", add_help_option=False) psr = ArgumentParser(prog="kcc-c2e", usage="kcc-c2e [options] [input]", add_help=False)
mainOptions = OptionGroup(psr, "MAIN") mandatory_options = psr.add_argument_group("MANDATORY")
processingOptions = OptionGroup(psr, "PROCESSING") main_options = psr.add_argument_group("MAIN")
outputOptions = OptionGroup(psr, "OUTPUT SETTINGS") processing_options = psr.add_argument_group("PROCESSING")
customProfileOptions = OptionGroup(psr, "CUSTOM PROFILE") output_options = psr.add_argument_group("OUTPUT SETTINGS")
otherOptions = OptionGroup(psr, "OTHER") custom_profile_options = psr.add_argument_group("CUSTOM PROFILE")
other_options = psr.add_argument_group("OTHER")
mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KV", mandatory_options.add_argument("input", action="extend", nargs="*", default=None,
help="Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KPW5, KV, KO, " help="Full path to comic folder or file(s) to be processed.")
"K11, KS, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoL, KoF, KoS, KoE)"
" [Default=KV]")
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="Manga style (right-to-left reading and splitting)")
mainOptions.add_option("-q", "--hq", action="store_true", dest="hq", default=False,
help="Try to increase the quality of magnification")
mainOptions.add_option("-2", "--two-panel", action="store_true", dest="autoscale", default=False,
help="Display two not four panels in Panel View mode")
mainOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
help="Webtoon processing mode"),
mainOptions.add_option("--targetsize", type="int", dest="targetsize", default=None,
help="the maximal size of output file in MB."
" [Default=100MB for webtoon and 400MB for others]")
outputOptions.add_option("-o", "--output", action="store", dest="output", default=None, main_options.add_argument("-p", "--profile", action="store", dest="profile", default="KV",
help="Output generated file to specified directory or file") help="Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KPW5, KV, KO, "
outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle", "K11, KS, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoL, KoF, KoS, KoE)"
help="Comic title [Default=filename or directory name]") " [Default=KV]")
outputOptions.add_option("-f", "--format", action="store", dest="format", default="Auto", main_options.add_argument("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB) " help="Manga style (right-to-left reading and splitting)")
"[Default=Auto]") main_options.add_argument("-q", "--hq", action="store_true", dest="hq", default=False,
outputOptions.add_option("-b", "--batchsplit", type="int", dest="batchsplit", default="0", help="Try to increase the quality of magnification")
help="Split output into multiple files. 0: Don't split 1: Automatic mode " main_options.add_argument("-2", "--two-panel", action="store_true", dest="autoscale", default=False,
"2: Consider every subdirectory as separate volume [Default=0]") help="Display two not four panels in Panel View mode")
main_options.add_argument("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
help="Webtoon processing mode"),
main_options.add_argument("--ts", "--targetsize", type=int, dest="targetsize", default=None,
help="the maximal size of output file in MB."
" [Default=100MB for webtoon and 400MB for others]")
processingOptions.add_option("-n", "--noprocessing", action="store_true", dest="noprocessing", default=False, output_options.add_argument("-o", "--output", action="store", dest="output", default=None,
help="Do not modify image and ignore any profil or processing option") help="Output generated file to specified directory or file")
processingOptions.add_option("-u", "--upscale", action="store_true", dest="upscale", default=False, output_options.add_argument("-t", "--title", action="store", dest="title", default="defaulttitle",
help="Resize images smaller than device's resolution") help="Comic title [Default=filename or directory name]")
processingOptions.add_option("-s", "--stretch", action="store_true", dest="stretch", default=False, output_options.add_argument("-f", "--format", action="store", dest="format", default="Auto",
help="Stretch images to device's resolution") help="Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB) "
processingOptions.add_option("-r", "--splitter", type="int", dest="splitter", default="0", "[Default=Auto]")
help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]") output_options.add_argument("-b", "--batchsplit", type=int, dest="batchsplit", default="0",
processingOptions.add_option("-g", "--gamma", type="float", dest="gamma", default="0.0", help="Split output into multiple files. 0: Don't split 1: Automatic mode "
help="Apply gamma correction to linearize the image [Default=Auto]") "2: Consider every subdirectory as separate volume [Default=0]")
processingOptions.add_option("-c", "--cropping", type="int", dest="cropping", default="2",
help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
processingOptions.add_option("--cp", "--croppingpower", type="float", dest="croppingp", default="1.0",
help="Set cropping power [Default=1.0]")
processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
help="Disable autodetection and force black borders")
processingOptions.add_option("--whiteborders", action="store_true", dest="white_borders", default=False,
help="Disable autodetection and force white borders")
processingOptions.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False,
help="Don't convert images to grayscale")
processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
help="Create PNG files instead JPEG")
processingOptions.add_option("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
help="Create JPEG files using mozJpeg")
processingOptions.add_option("--maximizestrips", action="store_true", dest="maximizestrips", default=False,
help="Turn 1x4 strips to 2x2 strips")
customProfileOptions.add_option("--customwidth", type="int", dest="customwidth", default=0,
help="Replace screen width provided by device profile")
customProfileOptions.add_option("--customheight", type="int", dest="customheight", default=0,
help="Replace screen height provided by device profile")
otherOptions.add_option("-h", "--help", action="help", processing_options.add_argument("-n", "--noprocessing", action="store_true", dest="noprocessing", default=False,
help="Show this help message and exit") help="Do not modify image and ignore any profil or processing option")
processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False,
help="Resize images smaller than device's resolution")
processing_options.add_argument("-s", "--stretch", action="store_true", dest="stretch", default=False,
help="Stretch images to device's resolution")
processing_options.add_argument("-r", "--splitter", type=int, dest="splitter", default="0",
help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]")
processing_options.add_argument("-g", "--gamma", type=float, dest="gamma", default="0.0",
help="Apply gamma correction to linearize the image [Default=Auto]")
processing_options.add_argument("-c", "--cropping", type=int, dest="cropping", default="2",
help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
processing_options.add_argument("--cp", "--croppingpower", type=float, dest="croppingp", default="1.0",
help="Set cropping power [Default=1.0]")
processing_options.add_argument("--cm", "--croppingminimum", type=float, dest="croppingm", default="0.0",
help="Set cropping minimum area ratio [Default=0.0]")
processing_options.add_argument("--blackborders", action="store_true", dest="black_borders", default=False,
help="Disable autodetection and force black borders")
processing_options.add_argument("--whiteborders", action="store_true", dest="white_borders", default=False,
help="Disable autodetection and force white borders")
processing_options.add_argument("--forcecolor", action="store_true", dest="forcecolor", default=False,
help="Don't convert images to grayscale")
processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False,
help="Create PNG files instead JPEG")
processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
help="Create JPEG files using mozJpeg")
processing_options.add_argument("--maximizestrips", action="store_true", dest="maximizestrips", default=False,
help="Turn 1x4 strips to 2x2 strips")
processing_options.add_argument("-d", "--delete", action="store_true", dest="delete", default=False,
help="Delete source file(s) or a directory. It's not recoverable.")
custom_profile_options.add_argument("--customwidth", type=int, dest="customwidth", default=0,
help="Replace screen width provided by device profile")
custom_profile_options.add_argument("--customheight", type=int, dest="customheight", default=0,
help="Replace screen height provided by device profile")
other_options.add_argument("-h", "--help", action="help",
help="Show this help message and exit")
psr.add_option_group(mainOptions)
psr.add_option_group(outputOptions)
psr.add_option_group(processingOptions)
psr.add_option_group(customProfileOptions)
psr.add_option_group(otherOptions)
return psr return psr
def checkOptions(options): def checkOptions(options):
options.panelview = True options.panelview = True
options.iskindle = False options.iskindle = False
options.isKobo = False
options.bordersColor = None options.bordersColor = None
options.keep_epub = False options.keep_epub = False
if options.format == 'EPUB-200MB': if options.format == 'EPUB-200MB':
options.targetsize = 200 options.targetsize = 195
options.format = 'EPUB' options.format = 'EPUB'
if options.batchsplit != 2: if options.batchsplit != 2:
options.batchsplit = 1 options.batchsplit = 1
@@ -993,6 +1024,7 @@ def checkOptions(options):
options.keep_epub = True options.keep_epub = True
options.format = 'MOBI' options.format = 'MOBI'
options.kfx = False options.kfx = False
options.supportSyntheticSpread = False
if options.format == 'Auto': if options.format == 'Auto':
if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KPW5', 'KV', 'KO', 'K11', 'KS']: if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KPW5', 'KV', 'KO', 'K11', 'KS']:
options.format = 'MOBI' options.format = 'MOBI'
@@ -1003,6 +1035,12 @@ def checkOptions(options):
options.format = 'CBZ' options.format = 'CBZ'
if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KPW5', 'KV', 'KO', 'K11', 'KS']: if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KPW5', 'KV', 'KO', 'K11', 'KS']:
options.iskindle = True options.iskindle = True
elif options.profile in ['OTHER', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O', 'KoAO', 'KoN', 'KoC', 'KoL', 'KoF', 'KoS', 'KoE']:
options.isKobo = True
# Other Kobo devices probably support synthetic spreads as well, but
# they haven't been tested.
if options.profile in ['KoF']:
options.supportSyntheticSpread = True
if options.white_borders: if options.white_borders:
options.bordersColor = 'white' options.bordersColor = 'white'
if options.black_borders: if options.black_borders:
@@ -1048,6 +1086,10 @@ def checkOptions(options):
image.ProfileData.Profiles["Custom"] = newProfile image.ProfileData.Profiles["Custom"] = newProfile
options.profile = "Custom" options.profile = "Custom"
options.profileData = image.ProfileData.Profiles[options.profile] options.profileData = image.ProfileData.Profiles[options.profile]
# kindle scribe conversion to mobi is limited in resolution by kindlegen, same with send to kindle and epub
if options.profile == 'KS' and (options.format == 'MOBI' or options.format == 'EPUB'):
options.profileData = list(options.profileData)
options.profileData[1] = (1440, 1920)
return options return options
@@ -1103,7 +1145,7 @@ def makeBook(source, qtgui=None):
y = image.ProfileData.Profiles[options.profile][1][1] y = image.ProfileData.Profiles[options.profile][1][1]
comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtgui) comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtgui)
if options.noprocessing: if options.noprocessing:
print("Do not process image, ignore any profil or processing option") print("Do not process image, ignore any profile or processing option")
else: else:
print("Processing images...") print("Processing images...")
if GUI: if GUI:
@@ -1112,8 +1154,6 @@ def makeBook(source, qtgui=None):
if GUI: if GUI:
GUI.progressBarTick.emit('1') GUI.progressBarTick.emit('1')
chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images')) chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
if 'Ko' in options.profile and options.format == 'CBZ':
sanitizeTreeKobo(os.path.join(path, 'OEBPS', 'Images'))
if options.batchsplit > 0: if options.batchsplit > 0:
tomes = splitDirectory(path) tomes = splitDirectory(path)
else: else:
@@ -1152,7 +1192,12 @@ def makeBook(source, qtgui=None):
else: else:
filepath.append(getOutputFilename(source, options.output, '.epub', '')) filepath.append(getOutputFilename(source, options.output, '.epub', ''))
makeZIP(tome + '_comic', tome, True) makeZIP(tome + '_comic', tome, True)
move(tome + '_comic.zip', filepath[-1]) 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) rmtree(tome, True)
if GUI: if GUI:
GUI.progressBarTick.emit('tick') GUI.progressBarTick.emit('tick')
@@ -1179,6 +1224,12 @@ def makeBook(source, qtgui=None):
os.remove(i.replace('.epub', '.mobi') + '_toclean') os.remove(i.replace('.epub', '.mobi') + '_toclean')
if k.path and k.coverSupport: if k.path and k.coverSupport:
options.covers[filepath.index(i)][0].saveToKindle(k, options.covers[filepath.index(i)][1]) options.covers[filepath.index(i)][0].saveToKindle(k, options.covers[filepath.index(i)][1])
if options.delete:
if os.path.isfile(source):
os.remove(source)
elif os.path.isdir(source):
rmtree(source)
return filepath return filepath
@@ -1224,7 +1275,8 @@ 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.terminate() output.communicate()
break
else: else:
# ERROR: EPUB too big # ERROR: EPUB too big
kindlegenErrorCode = 23026 kindlegenErrorCode = 23026

View File

@@ -20,15 +20,11 @@
import os import os
import sys import sys
from argparse import ArgumentParser
from shutil import rmtree, copytree, move from shutil import rmtree, copytree, move
from optparse import OptionParser, OptionGroup
from multiprocessing import Pool from multiprocessing import Pool
from PIL import Image, ImageChops, ImageOps, ImageDraw from PIL import Image, ImageChops, ImageOps, ImageDraw
from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace
try:
from PyQt5 import QtCore
except ImportError:
QtCore = None
def mergeDirectoryTick(output): def mergeDirectoryTick(output):
@@ -102,7 +98,7 @@ def splitImage(work):
opt = work[2] opt = work[2]
filePath = os.path.join(path, name) filePath = os.path.join(path, name)
Image.warnings.simplefilter('error', Image.DecompressionBombWarning) Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
Image.MAX_IMAGE_PIXELS = 1000000000 Image.MAX_IMAGE_PIXELS = 1000000000
imgOrg = Image.open(filePath).convert('RGB') imgOrg = Image.open(filePath).convert('RGB')
imgProcess = Image.open(filePath).convert('1') imgProcess = Image.open(filePath).convert('1')
widthImg, heightImg = imgOrg.size widthImg, heightImg = imgOrg.size
@@ -116,7 +112,7 @@ def splitImage(work):
panelDetected = False panelDetected = False
panels = [] panels = []
while yWork < heightImg: while yWork < heightImg:
tmpImg = imgProcess.crop([4, yWork, widthImg-4, yWork + 4]) tmpImg = imgProcess.crop((4, yWork, widthImg-4, yWork + 4))
solid = detectSolid(tmpImg) solid = detectSolid(tmpImg)
if not solid and not panelDetected: if not solid and not panelDetected:
panelDetected = True panelDetected = True
@@ -149,7 +145,7 @@ def splitImage(work):
if opt.debug: if opt.debug:
for panel in panelsProcessed: for panel in panelsProcessed:
draw.rectangle([(0, panel[0]), (widthImg, panel[1])], (0, 255, 0, 128), (0, 0, 255, 255)) draw.rectangle(((0, panel[0]), (widthImg, panel[1])), (0, 255, 0, 128), (0, 0, 255, 255))
debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg) debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg)
debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG') debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG')
@@ -182,7 +178,7 @@ def splitImage(work):
if pageHeight > 15: if pageHeight > 15:
newPage = Image.new('RGB', (widthImg, pageHeight)) newPage = Image.new('RGB', (widthImg, pageHeight))
for panel in page: for panel in page:
panelImg = imgOrg.crop([0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]]) panelImg = imgOrg.crop((0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]))
newPage.paste(panelImg, (0, targetHeight)) newPage.paste(panelImg, (0, targetHeight))
targetHeight += panelsProcessed[panel][2] targetHeight += panelsProcessed[panel][2]
newPage.save(os.path.join(path, os.path.splitext(name)[0] + '-' + str(pageNumber) + '.png'), 'PNG') newPage.save(os.path.join(path, os.path.splitext(name)[0] + '-' + str(pageNumber) + '.png'), 'PNG')
@@ -193,97 +189,100 @@ def splitImage(work):
def main(argv=None, qtgui=None): def main(argv=None, qtgui=None):
global options, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput global args, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput
parser = OptionParser(usage="Usage: kcc-c2p [options] comic_folder", add_help_option=False) parser = ArgumentParser(prog="kcc-c2p", usage="kcc-c2p [options] [input]", add_help=False)
mainOptions = OptionGroup(parser, "MANDATORY")
otherOptions = OptionGroup(parser, "OTHER") mandatory_options = parser.add_argument_group("MANDATORY")
mainOptions.add_option("-y", "--height", type="int", dest="height", default=0, main_options = parser.add_argument_group("MAIN")
help="Height of the target device screen") other_options = parser.add_argument_group("OTHER")
mainOptions.add_option("-i", "--in-place", action="store_true", dest="inPlace", default=False, mandatory_options.add_argument("input", action="extend", nargs="*", default=None,
help="Overwrite source directory") help="Full path to comic folder(s) to be processed. Separate multiple inputs"
mainOptions.add_option("-m", "--merge", action="store_true", dest="merge", default=False, " with spaces.")
help="Combine every directory into a single image before splitting") main_options.add_argument("-y", "--height", type=int, dest="height", default=0,
otherOptions.add_option("-d", "--debug", action="store_true", dest="debug", default=False, help="Height of the target device screen")
help="Create debug file for every split image") main_options.add_argument("-i", "--in-place", action="store_true", dest="inPlace", default=False,
otherOptions.add_option("-h", "--help", action="help", help="Overwrite source directory")
help="Show this help message and exit") main_options.add_argument("-m", "--merge", action="store_true", dest="merge", default=False,
parser.add_option_group(mainOptions) help="Combine every directory into a single image before splitting")
parser.add_option_group(otherOptions) other_options.add_argument("-d", "--debug", action="store_true", dest="debug", default=False,
options, args = parser.parse_args(argv) help="Create debug file for every split image")
other_options.add_argument("-h", "--help", action="help",
help="Show this help message and exit")
args = parser.parse_args(argv)
if qtgui: if qtgui:
GUI = qtgui GUI = qtgui
else: else:
GUI = None GUI = None
if len(args) != 1: if not argv or args.input == []:
parser.print_help() parser.print_help()
return 1 return 1
if options.height > 0: if args.height > 0:
options.sourceDir = args[0] for sourceDir in args.input:
options.targetDir = args[0] + "-Splitted" targetDir = sourceDir + "-Splitted"
if os.path.isdir(options.sourceDir): if os.path.isdir(sourceDir):
rmtree(options.targetDir, True) rmtree(targetDir, True)
copytree(options.sourceDir, options.targetDir) copytree(sourceDir, targetDir)
work = [] work = []
pagenumber = 1 pagenumber = 1
splitWorkerOutput = [] splitWorkerOutput = []
splitWorkerPool = Pool(maxtasksperchild=10) splitWorkerPool = Pool(maxtasksperchild=10)
if options.merge: if args.merge:
print("Merging images...") print("Merging images...")
directoryNumer = 1 directoryNumer = 1
mergeWork = [] mergeWork = []
mergeWorkerOutput = [] mergeWorkerOutput = []
mergeWorkerPool = Pool(maxtasksperchild=10) mergeWorkerPool = Pool(maxtasksperchild=10)
mergeWork.append([options.targetDir]) mergeWork.append([targetDir])
for root, dirs, files in os.walk(options.targetDir, False): for root, dirs, files in os.walk(targetDir, False):
dirs, files = walkSort(dirs, files) dirs, files = walkSort(dirs, files)
for directory in dirs: for directory in dirs:
directoryNumer += 1 directoryNumer += 1
mergeWork.append([os.path.join(root, directory)]) mergeWork.append([os.path.join(root, directory)])
if GUI:
GUI.progressBarTick.emit('Combining images')
GUI.progressBarTick.emit(str(directoryNumer))
for i in mergeWork:
mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick)
mergeWorkerPool.close()
mergeWorkerPool.join()
if GUI and not GUI.conversionAlive:
rmtree(targetDir, True)
raise UserWarning("Conversion interrupted.")
if len(mergeWorkerOutput) > 0:
rmtree(targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
mergeWorkerOutput[0][1])
print("Splitting images...")
for root, _, files in os.walk(targetDir, False):
for name in files:
if getImageFileName(name) is not None:
pagenumber += 1
work.append([root, name, args])
else:
os.remove(os.path.join(root, name))
if GUI: if GUI:
GUI.progressBarTick.emit('Combining images') GUI.progressBarTick.emit('Splitting images')
GUI.progressBarTick.emit(str(directoryNumer)) GUI.progressBarTick.emit(str(pagenumber))
for i in mergeWork: GUI.progressBarTick.emit('tick')
mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick) if len(work) > 0:
mergeWorkerPool.close() for i in work:
mergeWorkerPool.join() splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImageTick)
if GUI and not GUI.conversionAlive: splitWorkerPool.close()
rmtree(options.targetDir, True) splitWorkerPool.join()
raise UserWarning("Conversion interrupted.") if GUI and not GUI.conversionAlive:
if len(mergeWorkerOutput) > 0: rmtree(targetDir, True)
rmtree(options.targetDir, True) raise UserWarning("Conversion interrupted.")
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0], if len(splitWorkerOutput) > 0:
mergeWorkerOutput[0][1]) rmtree(targetDir, True)
print("Splitting images...") raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
for root, _, files in os.walk(options.targetDir, False): splitWorkerOutput[0][1])
for name in files: if args.inPlace:
if getImageFileName(name) is not None: rmtree(sourceDir)
pagenumber += 1 move(targetDir, sourceDir)
work.append([root, name, options]) else:
else: rmtree(targetDir, True)
os.remove(os.path.join(root, name)) raise UserWarning("Source directory is empty.")
if GUI:
GUI.progressBarTick.emit('Splitting images')
GUI.progressBarTick.emit(str(pagenumber))
GUI.progressBarTick.emit('tick')
if len(work) > 0:
for i in work:
splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImageTick)
splitWorkerPool.close()
splitWorkerPool.join()
if GUI and not GUI.conversionAlive:
rmtree(options.targetDir, True)
raise UserWarning("Conversion interrupted.")
if len(splitWorkerOutput) > 0:
rmtree(options.targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
splitWorkerOutput[0][1])
if options.inPlace:
rmtree(options.sourceDir)
move(options.targetDir, options.sourceDir)
else: else:
rmtree(options.targetDir, True) raise UserWarning("Provided input is not a directory.")
raise UserWarning("Source directory is empty.")
else:
raise UserWarning("Provided path is not a directory.")
else: else:
raise UserWarning("Target height is not set.") raise UserWarning("Target height is not set.")

View File

@@ -19,6 +19,9 @@
# #
import os import os
import platform
import subprocess
import distro
from psutil import Popen from psutil import Popen
from shutil import move from shutil import move
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
@@ -38,19 +41,39 @@ class ComicArchive:
self.type = line.rstrip().decode().split(' = ')[1].upper() self.type = line.rstrip().decode().split(' = ')[1].upper()
break break
process.communicate() process.communicate()
if process.returncode != 0: if process.returncode != 0 and distro.id() == 'fedora':
raise OSError('Archive is corrupted or encrypted.') process = Popen('unrar l -y -p1 "' + self.filepath + '"', stderr=STDOUT, stdout=PIPE, stdin=PIPE, shell=True)
for line in process.stdout:
if b'Details: ' in line:
self.type = line.rstrip().decode().split(' ')[1].upper()
break
process.communicate()
if process.returncode != 0:
raise OSError('Archive is corrupted or encrypted.')
elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']:
raise OSError('Unsupported archive format.')
elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']: elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']:
raise OSError('Unsupported archive format.') 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 don\'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 = Popen('7z x -y -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' + targetdir + '" "' +
self.filepath + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) self.filepath + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
process.communicate() process.communicate()
if process.returncode != 0: if process.returncode != 0 and distro.id() == 'fedora':
raise OSError('Failed to extract archive.') process = Popen('unrar x -y -x__MACOSX -x.DS_Store -xthumbs.db -xThumbs.db "' + self.filepath + '" "' +
targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
process.communicate()
if process.returncode != 0:
raise OSError('Failed to extract archive.')
elif process.returncode != 0 and platform.system() == 'Darwin':
process = subprocess.run(f"unar '{self.filepath}' -f -o '{targetdir}'",
stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
if process.returncode != 0:
raise Exception(process.stdout.decode("utf-8"))
elif process.returncode != 0:
raise OSError('Failed to extract archive. Check if p7zip-rar is installed.')
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')

View File

@@ -24,6 +24,13 @@ 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
# 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:
def __init__(self): def __init__(self):
@@ -115,10 +122,10 @@ class ComicPageParser:
self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB') self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB')
self.color = self.colorCheck() self.color = self.colorCheck()
self.fill = self.fillCheck() self.fill = self.fillCheck()
self.splitCheck()
# backwards compatibility for Pillow >9.1.0 # backwards compatibility for Pillow >9.1.0
if not hasattr(Image, 'Resampling'): if not hasattr(Image, 'Resampling'):
Image.Resampling = Image Image.Resampling = Image
self.splitCheck()
def getImageHistogram(self, image): def getImageHistogram(self, image):
histogram = image.histogram() histogram = image.histogram()
@@ -265,6 +272,7 @@ class ComicPage:
if self.fill != 'white': if self.fill != 'white':
flags.append('BlackBackground') flags.append('BlackBackground')
if self.opt.forcepng: if self.opt.forcepng:
self.image.info["transparency"] = None
self.targetPath += '.png' self.targetPath += '.png'
self.image.save(self.targetPath, 'PNG', optimize=1) self.image.save(self.targetPath, 'PNG', optimize=1)
else: else:
@@ -305,36 +313,32 @@ class ComicPage:
self.image = self.image.quantize(palette=palImg) self.image = self.image.quantize(palette=palImg)
def resizeImage(self): def resizeImage(self):
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]: ratio_device = float(self.size[1]) / float(self.size[0])
method = Image.Resampling.BICUBIC ratio_image = float(self.image.size[1]) / float(self.image.size[0])
else: method = self.resize_method()
method = Image.Resampling.LANCZOS
if self.opt.stretch: if self.opt.stretch:
# if self.opt.stretch or (self.opt.kfx and ('-KCC-B' in self.targetPath or '-KCC-C' in self.targetPath)):
self.image = self.image.resize(self.size, method) self.image = self.image.resize(self.size, method)
elif self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1] and not self.opt.upscale: elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
if self.opt.format == 'CBZ' or self.opt.kfx: if self.opt.format == 'CBZ' or self.opt.kfx:
borderw = int((self.size[0] - self.image.size[0]) / 2) borderw = int((self.size[0] - self.image.size[0]) / 2)
borderh = int((self.size[1] - self.image.size[1]) / 2) borderh = int((self.size[1] - self.image.size[1]) / 2)
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill) self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]: if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]:
self.image = ImageOps.fit(self.image, self.size, method=Image.Resampling.BICUBIC, centering=(0.5, 0.5)) self.image = ImageOps.fit(self.image, self.size, method=method)
else: else: # if image bigger than device resolution or smaller with upscaling
if self.opt.format == 'CBZ' or self.opt.kfx: if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
ratioDev = float(self.size[0]) / float(self.size[1]) self.image = ImageOps.fit(self.image, self.size, method=method)
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev: elif self.opt.format == 'CBZ' or self.opt.kfx:
diff = int(self.image.size[1] * ratioDev) - self.image.size[0] self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=self.fill)
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.fill)
self.image = ImageOps.fit(self.image, self.size, method=method, centering=(0.5, 0.5))
else: else:
hpercent = self.size[1] / float(self.image.size[1]) self.image = ImageOps.contain(self.image, self.size, method=method)
wsize = int((float(self.image.size[0]) * float(hpercent)))
self.image = self.image.resize((wsize, self.size[1]), method) def resize_method(self):
if self.image.size[0] > self.size[0] or self.image.size[1] > self.size[1]: if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
self.image.thumbnail(self.size, Image.Resampling.LANCZOS) method = Image.Resampling.BICUBIC
else:
method = Image.Resampling.LANCZOS
return method
def getBoundingBox(self, tmptmg): def getBoundingBox(self, tmptmg):
min_margin = [int(0.005 * i + 0.5) for i in tmptmg.size] min_margin = [int(0.005 * i + 0.5) for i in tmptmg.size]
@@ -350,7 +354,13 @@ class ComicPage:
) )
return bbox return bbox
def cropPageNumber(self, power): def maybeCrop(self, box, minimum):
box_area = (box[2] - box[0]) * (box[3] - box[1])
image_area = self.image.size[0] * self.image.size[1]
if (box_area / image_area) >= minimum:
self.image = self.image.crop(box)
def cropPageNumber(self, power, minimum):
if self.fill != 'white': if self.fill != 'white':
tmptmg = self.image.convert(mode='L') tmptmg = self.image.convert(mode='L')
else: else:
@@ -359,16 +369,18 @@ class ComicPage:
tmptmg = tmptmg.filter(ImageFilter.MinFilter(size=3)) tmptmg = tmptmg.filter(ImageFilter.MinFilter(size=3))
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=5)) tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=5))
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x) tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
self.image = self.image.crop(tmptmg.getbbox()) if tmptmg.getbbox() else self.image if tmptmg.getbbox():
self.maybeCrop(tmptmg.getbbox(), minimum)
def cropMargin(self, power): def cropMargin(self, power, minimum):
if self.fill != 'white': if self.fill != 'white':
tmptmg = self.image.convert(mode='L') tmptmg = self.image.convert(mode='L')
else: else:
tmptmg = ImageOps.invert(self.image.convert(mode='L')) tmptmg = ImageOps.invert(self.image.convert(mode='L'))
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=3)) tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=3))
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x) tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
self.image = self.image.crop(self.getBoundingBox(tmptmg)) if tmptmg.getbbox() else self.image if tmptmg.getbbox():
self.maybeCrop(self.getBoundingBox(tmptmg), minimum)
class Cover: class Cover:
@@ -381,10 +393,10 @@ class Cover:
else: else:
self.tomeid = tomeid self.tomeid = tomeid
self.image = Image.open(source) self.image = Image.open(source)
self.process()
# backwards compatibility for Pillow >9.1.0 # backwards compatibility for Pillow >9.1.0
if not hasattr(Image, 'Resampling'): if not hasattr(Image, 'Resampling'):
Image.Resampling = Image Image.Resampling = Image
self.process()
def process(self): def process(self):
self.image = self.image.convert('RGB') self.image = self.image.convert('RGB')

View File

@@ -34,7 +34,8 @@ class MetadataParser:
'Inkers': [], 'Inkers': [],
'Colorists': [], 'Colorists': [],
'Summary': '', 'Summary': '',
'Bookmarks': []} 'Bookmarks': [],
'Title': ''}
self.rawdata = None self.rawdata = None
self.format = None self.format = None
if self.source.endswith('.xml') and os.path.exists(self.source): if self.source.endswith('.xml') and os.path.exists(self.source):
@@ -45,7 +46,7 @@ class MetadataParser:
self.rawdata = cbx.extractMetadata() self.rawdata = cbx.extractMetadata()
self.format = cbx.type self.format = cbx.type
except OSError as e: except OSError as e:
raise UserWarning(e.strerror) raise UserWarning(e)
if self.rawdata: if self.rawdata:
self.parseXML() self.parseXML()
@@ -58,6 +59,8 @@ class MetadataParser:
self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue self.data['Number'] = self.rawdata.getElementsByTagName('Number')[0].firstChild.nodeValue
if len(self.rawdata.getElementsByTagName('Summary')) != 0: if len(self.rawdata.getElementsByTagName('Summary')) != 0:
self.data['Summary'] = self.rawdata.getElementsByTagName('Summary')[0].firstChild.nodeValue self.data['Summary'] = self.rawdata.getElementsByTagName('Summary')[0].firstChild.nodeValue
if len(self.rawdata.getElementsByTagName('Title')) != 0:
self.data['Title'] = self.rawdata.getElementsByTagName('Title')[0].firstChild.nodeValue
for field in ['Writer', 'Penciller', 'Inker', 'Colorist']: for field in ['Writer', 'Penciller', 'Inker', 'Colorist']:
if len(self.rawdata.getElementsByTagName(field)) != 0: if len(self.rawdata.getElementsByTagName(field)) != 0:
for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '): for person in self.rawdata.getElementsByTagName(field)[0].firstChild.nodeValue.split(', '):
@@ -76,7 +79,8 @@ class MetadataParser:
for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']], for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']],
['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])], ['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])],
['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])], ['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])],
['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']]): ['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']],
['Title', self.data['Title']]):
if self.rawdata.getElementsByTagName(row[0]): if self.rawdata.getElementsByTagName(row[0]):
node = self.rawdata.getElementsByTagName(row[0])[0] node = self.rawdata.getElementsByTagName(row[0])[0]
if row[1]: if row[1]:
@@ -97,7 +101,8 @@ class MetadataParser:
for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']], for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']],
['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])], ['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])],
['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])], ['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])],
['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']]): ['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']],
['Title', self.data['Title']]):
if row[1]: if row[1]:
main = doc.createElement(row[0]) main = doc.createElement(row[0])
root.appendChild(main) root.appendChild(main)
@@ -116,5 +121,5 @@ class MetadataParser:
cbx = comicarchive.ComicArchive(self.source) cbx = comicarchive.ComicArchive(self.source)
cbx.addFile(tmpXML) cbx.addFile(tmpXML)
except OSError as e: except OSError as e:
raise UserWarning(e.strerror) raise UserWarning(e)
rmtree(workdir) rmtree(workdir)

View File

@@ -25,6 +25,11 @@ import os
from random import choice from random import choice
from string import ascii_uppercase, digits from string import ascii_uppercase, digits
# skip stray images a few pixels in size in some PDFs
# typical images are many thousands in length
# https://github.com/ciromattia/kcc/pull/546
STRAY_IMAGE_LENGTH_THRESHOLD = 300
class PdfJpgExtract: class PdfJpgExtract:
def __init__(self, fname): def __init__(self, fname):
@@ -60,10 +65,15 @@ class PdfJpgExtract:
raise Exception("Didn't find end of JPG!") raise Exception("Didn't find end of JPG!")
istart += startfix istart += startfix
iend += endfix iend += endfix
i = iend
if iend - istart < STRAY_IMAGE_LENGTH_THRESHOLD:
continue
jpg = pdf[istart:iend] jpg = pdf[istart:iend]
jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb") jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb")
jpgfile.write(jpg) jpgfile.write(jpg)
jpgfile.close() jpgfile.close()
njpg += 1 njpg += 1
i = iend
return self.path, njpg return self.path, njpg

View File

@@ -100,11 +100,11 @@ def dependencyCheck(level):
missing = [] missing = []
if level > 2: if level > 2:
try: try:
from PyQt5.QtCore import qVersion as qtVersion from PySide6.QtCore import qVersion as qtVersion
if StrictVersion('5.6.0') > StrictVersion(qtVersion()): if StrictVersion('6.5.1') > StrictVersion(qtVersion()):
missing.append('PyQt 5.6.0+') missing.append('PySide 6.5.1+')
except ImportError: except ImportError:
missing.append('PyQt 5.6.0+') missing.append('PySide 6.5.1+')
try: try:
import raven import raven
except ImportError: except ImportError:

Binary file not shown.

Binary file not shown.

View File

@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDisplayName</key>
<string>Kindle Comic Converter</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>cbz</string>
<string>cbr</string>
<string>cb7</string>
<string>zip</string>
<string>rar</string>
<string>7z</string>
<string>pdf</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>comic2ebook.icns</string>
<key>CFBundleTypeName</key>
<string>Comics</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>MacOS/Kindle Comic Converter</string>
<key>CFBundleGetInfoString</key>
<string>KindleComicConverter 5.5.2, written 2012-2019 by Ciro Mattia Gonano and Pawel Jastrzebski</string>
<key>CFBundleIconFile</key>
<string>comic2ebook.icns</string>
<key>CFBundleIdentifier</key>
<string>com.kindlecomicconverter.KindleComicConverter</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Kindle Comic Converter</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.5.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>5.5.2</string>
<key>LSEnvironment</key>
<dict>
<key>PATH</key>
<string>./../Resources:/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/MacOS:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>LSHasLocalizedDisplayName</key>
<false/>
<key>LSMinimumSystemVersion</key>
<string>10.14.0</string>
<key>NSAppleScriptEnabled</key>
<false/>
<key>NSHumanReadableCopyright</key>
<string>ISC License (ISCL)</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSRequiresAquaSystemAppearance</key>
<string>false</string>
<key>NSInitialToolTipDelay</key>
<integer>1000</integer>
</dict>
</plist>

Binary file not shown.

View File

@@ -1,7 +1,7 @@
PyQt5>=5.6.0 PySide6>=6.5.1
Pillow>=5.2.0 Pillow>=5.2.0
psutil>=5.0.0 psutil>=5.9.5
python-slugify>=1.2.1,<8.0.0 python-slugify>=1.2.1
raven>=6.0.0 raven>=6.0.0
# PyQt5-tools mozjpeg-lossless-optimization>=1.1.2
mozjpeg-lossless-optimization distro

View File

@@ -17,8 +17,6 @@ import setuptools
import distutils.cmd import distutils.cmd
from kindlecomicconverter import __version__ from kindlecomicconverter import __version__
OSX_INFO_PLIST = "other/osx/Info.plist"
NAME = 'KindleComicConverter' NAME = 'KindleComicConverter'
MAIN = 'kcc.py' MAIN = 'kcc.py'
VERSION = __version__ VERSION = __version__
@@ -39,22 +37,8 @@ class BuildBinaryCommand(distutils.cmd.Command):
def run(self): def run(self):
VERSION = __version__ VERSION = __version__
if sys.platform == 'darwin': if sys.platform == 'darwin':
with open(OSX_INFO_PLIST, 'r') as file:
filedata = file.read()
filedata = filedata.replace('5.5.2', VERSION)
with open(OSX_INFO_PLIST, 'w') as file:
file.write(filedata)
os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py') os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
os.makedirs('dist/Kindle Comic Converter.app/Contents/Resources/Codecs') # TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
shutil.copy('other/osx/7z', 'dist/Kindle Comic Converter.app/Contents/Resources')
shutil.copy('other/osx/7z.so', 'dist/Kindle Comic Converter.app/Contents/Resources')
shutil.copy('other/osx/Rar.so', 'dist/Kindle Comic Converter.app/Contents/Resources/Codecs')
shutil.copy('other/osx/Info.plist', 'dist/Kindle Comic Converter.app/Contents')
shutil.copy('LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources')
shutil.copy('other/windows/Additional-LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources')
os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/7z', 0o777)
os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg') os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg')
exit(0) exit(0)
elif sys.platform == 'win32': elif sys.platform == 'win32':
@@ -91,11 +75,12 @@ setuptools.setup(
}, },
packages=['kindlecomicconverter'], packages=['kindlecomicconverter'],
install_requires=[ install_requires=[
'PyQt5>=5.6.0', 'pyside6>=6.5.1',
'Pillow>=5.2.0', 'Pillow>=5.2.0',
'psutil>=5.0.0', 'psutil>=5.9.5',
'python-slugify>=1.2.1,<8.0.0', 'python-slugify>=1.2.1,<9.0.0',
'raven>=6.0.0', 'raven>=6.0.0',
'mozjpeg-lossless-optimization>=1.1.2',
], ],
classifiers=[], classifiers=[],
zip_safe=False, zip_safe=False,