1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-15 13:38:46 +00:00

Compare commits

...

78 Commits

Author SHA1 Message Date
darodi
424118b7cd fix package-windows-with-docker.yml (#579) 2023-08-11 19:00:30 +00:00
Alex Xu
4e45402b8f add download instructions (#577)
* add download instructions

* fix spacing

* fix capitalization
2023-08-10 16:16:55 +00:00
Alex Xu
615790c278 remove old windows 7z binaries and Inno Setup Script ISS (#575)
* Delete 7z.dll

* Delete 7z.exe

* Delete Additional-LICENSE.txt

* Delete InstallWarning.rtf

* Update kcc.iss

* Update kcc.py

* Update .dockerignore

* Delete kcc.iss
2023-08-09 16:24:26 +00: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
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
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
a5202458dc Merge pull request #553
* warn p7zip-rar

* replace strerror
2023-07-05 16:54:24 +00:00
Alex Xu
5902d88d98 remove dead download links (#544)
* remove old links
2023-07-04 10:48:04 +00: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
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 1083 additions and 1479 deletions

View File

@@ -7,8 +7,7 @@ KindleComicConverter.egg-info
.gitignore .gitignore
.travis.yml .travis.yml
Dockerfile Dockerfile
other
venv venv
*.md *.md
LICENSE.txt LICENSE.txt
MANIFEST.in MANIFEST.in

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-pyqt5 python3-pip squashfs-tools libfuse2
python -m pip install --upgrade pip setuptools wheel pyinstaller python -m pip install --upgrade pip setuptools wheel certifi pyinstaller PyQt6 --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@python3-10-pyinstaller-5-3
with:
path: .
spec: ./kcc.spec
- name: Package Application
uses: JackMcKew/pyinstaller-action-windows@python3-10-pyinstaller-5-3
with:
path: .
spec: ./kcc-c2e.spec
- name: Package Application
uses: JackMcKew/pyinstaller-action-windows@python3-10-pyinstaller-5-3
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 }}

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-20230514
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"]

162
Dockerfile-base Normal file
View File

@@ -0,0 +1,162 @@
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 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-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+=(python3-pyqt5) && \
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+=(python3-pyqt5) && \
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-20230514 > /IMAGE_VERSION

358
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,138 +32,20 @@ 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:
- **https://github.com/ciromattia/kcc/releases** - **https://github.com/ciromattia/kcc/releases**
Click on **Assets** of the latest release.
~~- **[Windows](http://kcc.iosphe.re/Windows/) (64-bit only)**~~ You probably want either
~~- **[macOS](http://kcc.iosphe.re/OSX/) (10.14+)**~~ - `kcc_*.*.*.exe` (Windows)
~~- **Linux:** Currently unavailable.~~ - `KindleComicConverter_osx_*.*.*.dmg` (Mac)
Installation Wiki: https://github.com/ciromattia/kcc/wiki/Installation
- flatpak : https://flathub.org/apps/details/io.github.ciromattia.kcc
#### MacOS installation - Docker: https://github.com/ciromattia/kcc/pkgs/container/kcc
##### 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:
@@ -181,92 +57,28 @@ Following software is required to run Linux version of **KCC** and/or bare sourc
- [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 python3-pyqt5 && \
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 - Qt platform integration plugin for Deepin Desktop Environment
```bash ```bash
$ sudo apt-get install qt5dxcb-plugin $ sudo apt-get install qt5dxcb-plugin
``` ```
- 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) - KindleGen ~~[(deprecated link)](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211)~~ v2.9+ (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. - should be placed in a directory reachable by your _PATH_ or in _KCC_ directory
- `KindleGen` can be found in [Kindle Previewer](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011)
- `KindleGen` can be also be found in [Kindle Comic Creator](https://www.amazon.com/b?node=23496309011)
- [7z](http://www.7-zip.org/download.html) *(For CBZ/ZIP, CBR/RAR, 7z/CB7 support)* - [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 +98,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 +137,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.0.0
- python-slugify>=1.2.1
- raven>=6.0.0
- distro
- pip
- pip:
- mozjpeg-lossless-optimization>=1.1.2
- PyQt5>=5.6.0

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')

123
kcc.iss
View File

@@ -1,123 +0,0 @@
#define MyAppName "Kindle Comic Converter"
#define MyAppVersion "5.5.2"
#define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski"
#define MyAppURL "http://kcc.iosphe.re/"
#define MyAppExeName "KCC.exe"
[Setup]
AppId={{7D279A59-C65E-4DA7-B165-56DD06596216}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
AppCopyright=Copyright (C) 2012-2019 Ciro Mattia Gonano and Paweł Jastrzębski
ArchitecturesAllowed=x64
DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName}
AllowNoIcons=yes
LicenseFile=LICENSE.txt
OutputBaseFilename=KindleComicConverter_win_{#MyAppVersion}
SetupIconFile=icons\comic2ebook.ico
SolidCompression=yes
ShowLanguageDialog=no
LanguageDetectionMethod=none
WizardImageFile=icons\Wizard.bmp
WizardSmallImageFile=icons\Wizard-Small.bmp
UninstallDisplayName={#MyAppName}
UninstallDisplayIcon={app}\{#MyAppExeName}
ChangesAssociations=True
InfoAfterFile=other\windows\InstallWarning.rtf
SignTool=SignTool /d $q{#MyAppName}$q /du $q{#MyAppURL}$q $f
MinVersion=0,6.0
OutputDir=dist
ArchitecturesInstallIn64BitMode=x64
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "CBZassociation"; Description: "CBZ"; GroupDescription: "File associations:"
Name: "CBRassociation"; Description: "CBR"; GroupDescription: "File associations:"
Name: "CB7association"; Description: "CB7"; GroupDescription: "File associations:"
[Files]
Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion solidbreak
Source: "other\windows\Additional-LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion
Source: "other\windows\7z.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "other\windows\7z.dll"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\Readme"; Filename: "https://github.com/ciromattia/kcc#kcc"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall
[Messages]
WelcomeLabel1=Welcome to the KCC Setup Wizard
FinishedHeadingLabel=Completing the KCC Setup Wizard
[Registry]
Root: HKCR; SubKey: ".cbz"; ValueType: string; ValueData: "KCCZIP"; Flags: uninsdeletekey; Tasks: CBZassociation
Root: HKCR; SubKey: "KCCZIP"; ValueType: string; ValueData: "KCC ZIP Archive"; Flags: uninsdeletekey; Tasks: CBZassociation
Root: HKCR; SubKey: "KCCZIP\Shell\Open\Command"; ValueType: string; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey; Tasks: CBZassociation
Root: HKCR; Subkey: "KCCZIP\DefaultIcon"; ValueType: string; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletevalue; Tasks: CBZassociation
Root: HKCR; SubKey: ".cbr"; ValueType: string; ValueData: "KCCRAR"; Flags: uninsdeletekey; Tasks: CBRassociation
Root: HKCR; SubKey: "KCCRAR"; ValueType: string; ValueData: "KCC RAR Archive"; Flags: uninsdeletekey; Tasks: CBRassociation
Root: HKCR; SubKey: "KCCRAR\Shell\Open\Command"; ValueType: string; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey; Tasks: CBRassociation
Root: HKCR; Subkey: "KCCRAR\DefaultIcon"; ValueType: string; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletevalue; Tasks: CBRassociation
Root: HKCR; SubKey: ".cb7"; ValueType: string; ValueData: "KCCCB7"; Flags: uninsdeletekey; Tasks: CB7association
Root: HKCR; SubKey: "KCCCB7"; ValueType: string; ValueData: "KCC 7z Archive"; Flags: uninsdeletekey; Tasks: CB7association
Root: HKCR; SubKey: "KCCCB7\Shell\Open\Command"; ValueType: string; ValueData: """{app}\{#MyAppExeName}"" ""%1"""; Flags: uninsdeletekey; Tasks: CB7association
Root: HKCR; Subkey: "KCCCB7\DefaultIcon"; ValueType: string; ValueData: "{app}\{#MyAppExeName},0"; Flags: uninsdeletevalue; Tasks: CB7association
[Code]
function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
Result := sUnInstallString;
end;
function IsUpgrade(): Boolean;
begin
Result := (GetUninstallString() <> '');
end;
function UnInstallOldVersion(): Integer;
var
sUnInstallString: String;
iResultCode: Integer;
begin
Result := 0;
sUnInstallString := GetUninstallString();
if sUnInstallString <> '' then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep=ssInstall) then
begin
if (IsUpgrade()) then
begin
UnInstallOldVersion();
end;
end;
end;

41
kcc.py
View File

@@ -19,25 +19,44 @@
# 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.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,11 +16,13 @@
# 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 urlretrieve, urlopen
from time import sleep from time import sleep
from shutil import move, rmtree from shutil import move, rmtree
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
@@ -112,12 +114,6 @@ class Icons:
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.Normal, QtGui.QIcon.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.Normal, QtGui.QIcon.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.Normal, QtGui.QIcon.Off)
@@ -142,64 +138,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,12 +214,13 @@ 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() == 1:
@@ -294,6 +254,8 @@ 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.deleteBox.isChecked():
options.delete = True
if GUI.mozJpegBox.checkState() == 1: if GUI.mozJpegBox.checkState() == 1:
options.forcepng = True options.forcepng = True
elif GUI.mozJpegBox.checkState() == 2: elif GUI.mozJpegBox.checkState() == 2:
@@ -314,7 +276,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 +326,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')
@@ -504,7 +466,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'):
@@ -615,7 +578,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 +644,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 +659,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)
@@ -729,8 +693,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.Ok) QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.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':
@@ -777,22 +741,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)
@@ -823,6 +786,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'mozJpegBox': GUI.mozJpegBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(),
'widthBox': GUI.widthBox.value(), 'widthBox': GUI.widthBox.value(),
'heightBox': GUI.heightBox.value(), 'heightBox': GUI.heightBox.value(),
'deleteBox': GUI.deleteBox.checkState(),
'maximizeStrips': GUI.maximizeStrips.checkState(), 'maximizeStrips': GUI.maximizeStrips.checkState(),
'gammaSlider': float(self.gammaValue) * 100}) 'gammaSlider': float(self.gammaValue) * 100})
self.settings.sync() self.settings.sync()
@@ -885,21 +849,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 +913,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 +931,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',
@@ -1071,8 +1045,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 +1087,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":
@@ -1149,7 +1122,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
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-'):

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'gui/KCC.ui' # Form implementation generated from reading ui file 'gui/KCC.ui'
# #
# Created by: PyQt5 UI code generator 5.15.6 # Created by: PyQt5 UI code generator 5.15.7
# #
# WARNING: Any manual changes made to this file will be lost when pyuic5 is # 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. # run again. Do not edit this file unless you know what you are doing.
@@ -70,6 +70,9 @@ class Ui_mainWindow(object):
self.croppingBox.setTristate(True) self.croppingBox.setTristate(True)
self.croppingBox.setObjectName("croppingBox") 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.deleteBox = QtWidgets.QCheckBox(self.optionWidget)
self.deleteBox.setObjectName("deleteBox")
self.gridLayout_2.addWidget(self.deleteBox, 4, 1, 1, 1)
self.disableProcessingBox = QtWidgets.QCheckBox(self.optionWidget) self.disableProcessingBox = QtWidgets.QCheckBox(self.optionWidget)
self.disableProcessingBox.setObjectName("disableProcessingBox") self.disableProcessingBox.setObjectName("disableProcessingBox")
self.gridLayout_2.addWidget(self.disableProcessingBox, 4, 2, 1, 1) self.gridLayout_2.addWidget(self.disableProcessingBox, 4, 2, 1, 1)
@@ -254,7 +257,8 @@ class Ui_mainWindow(object):
mainWindow.setTabOrder(self.colorBox, self.croppingBox) mainWindow.setTabOrder(self.colorBox, self.croppingBox)
mainWindow.setTabOrder(self.croppingBox, self.mozJpegBox) mainWindow.setTabOrder(self.croppingBox, self.mozJpegBox)
mainWindow.setTabOrder(self.mozJpegBox, self.maximizeStrips) mainWindow.setTabOrder(self.mozJpegBox, self.maximizeStrips)
mainWindow.setTabOrder(self.maximizeStrips, self.disableProcessingBox) mainWindow.setTabOrder(self.maximizeStrips, self.deleteBox)
mainWindow.setTabOrder(self.deleteBox, self.disableProcessingBox)
mainWindow.setTabOrder(self.disableProcessingBox, self.editorButton) mainWindow.setTabOrder(self.disableProcessingBox, self.editorButton)
mainWindow.setTabOrder(self.editorButton, self.wikiButton) mainWindow.setTabOrder(self.editorButton, self.wikiButton)
mainWindow.setTabOrder(self.wikiButton, self.jobList) mainWindow.setTabOrder(self.wikiButton, self.jobList)
@@ -290,7 +294,9 @@ class Ui_mainWindow(object):
self.maximizeStrips.setText(_translate("mainWindow", "1x4 to 2x2 strips")) self.maximizeStrips.setText(_translate("mainWindow", "1x4 to 2x2 strips"))
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.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.croppingBox.setText(_translate("mainWindow", "Cropping mode")) self.croppingBox.setText(_translate("mainWindow", "Cropping mode"))
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.deleteBox.setToolTip(_translate("mainWindow", "Delete input file(s) or directory. It\'s not recoverable!"))
self.deleteBox.setText(_translate("mainWindow", "Delete input"))
self.disableProcessingBox.setToolTip(_translate("mainWindow", "<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>"))
self.disableProcessingBox.setText(_translate("mainWindow", "Disable processing")) self.disableProcessingBox.setText(_translate("mainWindow", "Disable processing"))
self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto")) self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto"))
self.croppingPowerLabel.setText(_translate("mainWindow", "Cropping power:")) self.croppingPowerLabel.setText(_translate("mainWindow", "Cropping power:"))

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,11 +28,10 @@ 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
@@ -54,23 +54,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 +295,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 +340,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 +514,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 +559,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 +601,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 +644,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 +708,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 +761,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 +788,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 +910,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 +932,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 +1028,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 +1039,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 +1090,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 +1149,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 +1158,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 +1196,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 +1228,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 +1279,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,8 +20,8 @@
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
@@ -102,7 +102,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 +116,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 +149,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 +182,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 +193,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

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.

Binary file not shown.

Binary file not shown.

View File

@@ -1,90 +0,0 @@
7-Zip
~~~~~
License for use and distribution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7-Zip Copyright (C) 1999-2018 Igor Pavlov.
The licenses for files are:
1) 7z.dll:
- The "GNU LGPL" as main license for most of the code
- The "GNU LGPL" with "unRAR license restriction" for some code
- The "BSD 3-clause License" for some code
2) All other files: the "GNU LGPL".
Redistributions in binary form must reproduce related license information from this file.
Note:
You can use 7-Zip on any computer, including a computer in a commercial
organization. You don't need to register or pay for 7-Zip.
GNU LGPL information
--------------------
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You can receive a copy of the GNU Lesser General Public License from
http://www.gnu.org/
BSD 3-clause License
--------------------
The "BSD 3-clause License" is used for the code in 7z.dll that implements LZFSE data decompression.
That code was derived from the code in the "LZFSE compression library" developed by Apple Inc,
that also uses the "BSD 3-clause License":
----
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----
unRAR license restriction
-------------------------
The decompression engine for RAR archives was developed using source
code of unRAR program.
All copyrights to original unRAR code are owned by Alexander Roshal.
The license for original unRAR code has the following restriction:
The unRAR sources cannot be used to re-create the RAR compression algorithm,
which is proprietary. Distribution of modified unRAR sources in separate form
or as a part of other software is permitted, provided that it is clearly
stated in the documentation and source comments that the code may
not be used to develop a RAR (WinRAR) compatible archiver.
--
Igor Pavlov

View File

@@ -1,233 +0,0 @@
{\rtf1\adeflang1025\ansi\ansicpg1250\uc1\adeff0\deff0\stshfdbch0\stshfloch31506\stshfhich31506\stshfbi31506\deflang1045\deflangfe1045\themelang1045\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\f0\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f37\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0502020204030204}Calibri;}
{\flomajor\f31500\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbmajor\f31501\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fhimajor\f31502\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0302020204030204}Calibri Light;}{\fbimajor\f31503\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\flominor\f31504\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fdbminor\f31505\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}
{\fhiminor\f31506\fbidi \fswiss\fcharset238\fprq2{\*\panose 020f0502020204030204}Calibri;}{\fbiminor\f31507\fbidi \froman\fcharset238\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f44\fbidi \froman\fcharset0\fprq2 Times New Roman;}
{\f43\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f45\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f46\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f47\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\f48\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f49\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f50\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f44\fbidi \froman\fcharset0\fprq2 Times New Roman;}
{\f43\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\f45\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f46\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f47\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\f48\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f49\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f50\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f414\fbidi \fswiss\fcharset0\fprq2 Calibri;}
{\f413\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\f415\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f416\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f417\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}
{\f418\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\f419\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f420\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\flomajor\f31510\fbidi \froman\fcharset0\fprq2 Times New Roman;}
{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31520\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
{\fhimajor\f31530\fbidi \fswiss\fcharset0\fprq2 Calibri Light;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}
{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);}
{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31540\fbidi \froman\fcharset0\fprq2 Times New Roman;}
{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31550\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
{\fdbminor\f31560\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31570\fbidi \fswiss\fcharset0\fprq2 Calibri;}
{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}
{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31580\fbidi \froman\fcharset0\fprq2 Times New Roman;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}
{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;
\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\chyperlink\ctint255\cshade255\red5\green99\blue193;\cfollowedhyperlink\ctint255\cshade255\red149\green79\blue114;}{\*\defchp
\f31506\fs22\lang1045\langfe1033\langfenp1033 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1045\langfe1033\cgrid\langnp1045\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive
\ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1
\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1045\langfe1033\cgrid\langnp1045\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\*\cs15 \additive
\rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf17 \sbasedon10 \sunhideused \styrsid3562894 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf18 \sbasedon10 \ssemihidden \sunhideused \styrsid7678248 FollowedHyperlink;}}{\*\rsidtbl \rsid1081196
\rsid3146412\rsid3562894\rsid5731975\rsid7678248\rsid9265883\rsid11107340\rsid11629590\rsid12600926\rsid13187577}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info
{\author Pawe\'b3 Jastrz\'eabski}{\operator Pawe\'b3 Jastrz\'eabski}{\creatim\yr2013\mo10\dy29\hr15\min17}{\revtim\yr2017\mo8\dy20\hr17\min40}{\version9}{\edmins8}{\nofpages1}{\nofwords33}{\nofchars201}{\nofcharsws233}{\vern39}}{\*\xmlnstbl {\xmlns1 http:
//schemas.microsoft.com/office/word/2003/wordml}}\paperw11906\paperh16838\margl1417\margr1417\margt1417\margb1417\gutter0\ltrsect
\deftab708\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0
\showxmlerrors1\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1417\dgvorigin1417\dghshow1\dgvshow1
\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct
\asianbrkrule\rsidroot11107340\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0
{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2
\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6
\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang
{\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs22\alang1025 \ltrch\fcs0
\f31506\fs22\lang1045\langfe1033\cgrid\langnp1045\langfenp1033 {\rtlch\fcs1 \af0 \ltrch\fcs0 \b\fs52\cf6\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid3562894 Warning!}{\rtlch\fcs1 \af0 \ltrch\fcs0
\b\fs52\cf6\lang2057\langfe1033\langnp2057\insrsid13187577\charrsid3562894
\par }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid1081196 Creation of}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 MOBI}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 files }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
\fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 require}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid11629590 s}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
\fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 additional software.}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926
\par }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 Please download: }{\field\flddirty{\*\fldinst {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 HYPERLINK "http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211" }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926
{\*\datafield
00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b9600000068007400740070003a002f002f007700770077002e0061006d0061007a006f006e002e0063006f006d002f00670070002f0066006500610074007500720065002e00680074006d006c003f00690065003d00
5500540046003800260064006f006300490064003d0031003000300030003700360035003200310031000000795881f43b1d7f48af2c825dc485276300000000a5ab00000000}}}{\fldrslt {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
\cs15\b\fs28\ul\cf17\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 KindleGen}}}\sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926
\par }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 And place }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \i\fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 kindlegen.exe}{\rtlch\fcs1
\af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid5731975\charrsid12600926 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 inside }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412\charrsid3146412 Kindle}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412\charrsid3146412 Comic}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
\b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412\charrsid3146412 Converter}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \b\fs28\lang2057\langfe1033\langnp2057\insrsid3146412 }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0
\fs28\lang2057\langfe1033\langnp2057\insrsid3562894\charrsid12600926 directory.
\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a
9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad
5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6
b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0
0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6
a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f
c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512
0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462
a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865
6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b
4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b
4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100b7e72e45da060000a81a0000160000007468656d652f7468656d652f
7468656d65312e786d6cec595d8b1b37147d2ff43f0cf3eef86bc61f4bbcc11edbd936bbc9123b2979d4dab24759cdc88ce4dd981028c963a1509a963e34d0b7
3e94b68104fa92fe9a6d53da14f2177aa5198f255bdb4d96149692352c63f9dcaba37bef9cab195dbe722fa2ce114e386171cb2d5f2ab90e8e476c4ce269cbbd
35ec171aaec3058ac788b218b7dc05e6ee95ed0f3fb88cb6448823ec807dccb750cb0d85986d158b7c04c3885f62331cc36f13964448c0d7645a1c27e818fc46
b45829956ac50891d875621481db3d2616c7ce0c1d122ce6ce8dc9848cb0bbbd9ca44761a658703930a2c9404e8133cb7d65a39b8c0fcb12c8173ca089738468
cb8569c7ec7888ef09d7a1880bf8a1e596d49f5bdcbe5c445b991115a7d86a767df597d96506e3c38a9a33991ee4937a9eefd5dab97f05a06213d7abf76abd5a
ee4f01d068040b4eb9e83efd4eb3d3f533ac064a2f2dbebbf56eb56ce035ffd50dce6d5f7e0cbc02a5febd0d7cbf1f40140dbc02a5787f03ef79f54ae0197805
4af1b50d7cbdd4ee7a7503af402125f1e106bae4d7aac172b53964c2e88e15def4bd7ebd92395fa1a01af22293534c582cce28b908dd65491f70124f9120b123
16333c4123a8ed005172901067974c43a8bf198a1987e152a5d42f55e1bffc78ea4a05066d61a4594b7a40886f0c495a0e1f2564265aeec7e0d5d520af5ffcf8
fac533e7e4e1f39387bf9c3c7a74f2f0e7d49161b583e2a96ef5eafb2ffe7ef2a9f3d7b3ef5e3dfeca8ee73afef79f3efbedd72fed4058e92a042fbf7efac7f3
a72fbff9fccf1f1e5be0ed041de8f021893077aee363e7268b60612a0426737c90bc9dc5304444b768c7538e622467b1f8ef89d0405f5f208a2cb80e3623783b
01a5b101afceef1a8407613217c4e2f15a1819c03dc6688725d6285c937369611ecee3a97df264aee36e2274649b3b40b191dfde7c064a4b6c2e83101b34f729
8a059ae2180b47fec60e31b6acee0e21465cf7c828619c4d847387381d44ac21199203a39a56463b2482bc2c6c0421df466cf66e3b1d466dabeee22313097705
a216f2434c8d305e457381229bcb218aa81ef05d24421bc9c12219e9b81e1790e929a6cce98d31e7369b1b09ac574bfa3590177bdaf7e8223291892087369fbb
88311dd965874188a2990d3b2071a8633fe28750a2c8d967c206df63e61d22bf431e507c6aba6fc31e409fe06c35b805caaa5bac0a44fe324f2cb9bc8a9951bf
83059d20aca406f4dfd0f388c4678afb9aacfbffadac8390befcf68965551755d0db09b1de513b6b327e1a6e5dbc03968cc9c5d7ee2e9ac7fb186e97cd06f65e
badf4bb7fbbf97eed3eee7772fd82b8d06f9965bc574c7aef6efd159dbf709a174201614ef72b583e7d0a0c67d1894e6ea8116e74f75b3102ee50d0df318b869
82948d9330f10911e1204433d8e6975de964ca33d753eecc1887ddbf1ab6fa96783a8ff6d8387d782d97e5836aaa211c89d578c9cfc7e18943a4e85a7df54096
bb576ca7eaf9794940dabe0d096d329344d542a2be1c9441524feb10340b09b5b277c2a26961d190ee97a9da6001d4f2acc00eca817d57cbf53d30012378b042
148f659ed2542fb3ab92f92e337d5a308d0a80edc4b20256996e4aaea72e4fae2e2db537c8b441422b3793848a8c6a653c44639c55a71c7d131a6f9bebe62aa5
063d190a351f94d68a46bdf16f2cce9b6bb05bd7061aeb4a4163e7b8e5d6aa3e94cc08cd5aee049efee1329a41ed70b9f345740a2fd64622496ff8f328cb2ce1
a28b7898065c894eaa061111387128895aae5c7e9e061a2b0d51dcca1510840b4bae09b272d1c841d2cd24e3c9048f849e766d44463afd0a0a9f6a85f557657e
7eb0b4647348f7201c1f3b07749edc4450627ebd2c0338261c5e0295d3688e09bcdccc856c557f6b8d29935dfdeda2aaa1741cd15988b28ea28b790a57529ed3
51dff21868dfb2354340b590648df0602a1bac1e54a39be65d23e5706ad73ddb48464e13cd55cf345445764dbb8a19332cdbc05a2ccfd7e43556cb1083a6e91d
3e95ee75c96d2eb56e6d9f90770908781e3f4bd77d8386a0515b4d6650938c3765586a76366af68ee502cfa0f6264d4253fddad2ed5adcf21e619d0e06cfd5f9
c16ebd6a6168b2dc5eaa48ab4311fdbc821ddc05f1e8c2bbe039155ca5128e2112041ba281da93a4b201b7c83d91dd1a70e5cc13d272ef97fcb61754fca0506a
f8bd8257f54a8586dfae16dabe5f2df7fc72a9dba93c80c622c2a8eca707327d78234517d9b18c1adf389a89962fdd2e8d585464eab0a5a888aba39972c5389a
490f639ca13c73711d02a273bf56e937abcd4eadd0acb6fb05afdb69149a41ad53e8d6827ab7df0dfc46b3ffc0758e14d86b5703afd66b146ae5202878b592a4
df6816ea5ea5d2f6eaed46cf6b3fc8b631b0f2543eb258407815afed7f000000ffff0300504b0304140006000800000021000dd1909fb60000001b0100002700
00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0ad
d40384e4350d363f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b
284d262452282e3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f16
5dfe514173d9850528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c0200001300000000
000000000000000000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b000000
00000000000000000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c00000000000000000000
000000190200007468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d0014000600080000002100b7e72e45da060000a81a000016
00000000000000000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b
0100002700000000000000000000000000e40900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000df0a00000000}
{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d
617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169
6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363
656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e}
{\*\latentstyles\lsdstimax375\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9;
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3;
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6;
\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;
\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;
\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;
\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;
\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;
\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;
\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;
\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;
\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;
\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;
\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5;
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5;
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5;
\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6;
\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6;
\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6;
\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis;
\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography;
\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4;
\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4;
\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1;
\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1;
\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2;
\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2;
\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3;
\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4;
\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4;
\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5;
\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5;
\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6;
\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6;
\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark;
\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1;
\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1;
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2;
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3;
\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3;
\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4;
\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4;
\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5;
\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5;
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6;
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;}}{\*\datastore 010500000200000018000000
4d73786d6c322e534158584d4c5265616465722e362e30000000000000000000000e0000
d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000000200000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
fffffffffffffffffdffffff04000000feffffff05000000fefffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffff010000000c6ad98892f1d411a65f0040963251e5000000000000000000000000b00f
9da8ca19d30103000000c0020000000000004d0073006f004400610074006100530074006f0072006500000000000000000000000000000000000000000000000000000000000000000000000000000000001a000101ffffffffffffffff020000000000000000000000000000000000000000000000b00f9da8ca19d301
b00f9da8ca19d301000000000000000000000000ca0041004300c300d300d300c70058004d00d4003000c9004d00c200590043003100320055004a00300051003d003d000000000000000000000000000000000032000101ffffffffffffffff030000000000000000000000000000000000000000000000b00f9da8ca19
d301b00f9da8ca19d3010000000000000000000000004900740065006d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000201ffffffff04000000ffffffff000000000000000000000000000000000000000000000000
00000000000000000000000000000000320100000000000001000000020000000300000004000000feffffff060000000700000008000000090000000a000000feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d226e6f223f3e3c623a536f75726365732053656c65637465645374796c653d225c41504153697874684564697469
6f6e4f66666963654f6e6c696e652e78736c22205374796c654e616d653d22415041222056657273696f6e3d22362220786d6c6e733a623d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879222078
6d6c6e733d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879223e3c2f623a536f75726365733e00000000000000000000000000003c3f786d6c2076657273696f6e3d22312e302220656e636f6469
6e673d225554462d3822207374616e64616c6f6e653d226e6f223f3e0d0a3c64733a6461746173746f72654974656d2064733a6974656d49443d227b43464133303041382d443733392d343633332d413933322d3236303236444335303936397d2220786d6c6e733a64733d22687474703a2f2f736368656d61732e6f70
656e786d6c666f726d6174732e6f72672f6f6666696365446f63756d656e742f323030362f637573500072006f007000650072007400690065007300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000200ffffffffffffffffffffffff000000000000
0000000000000000000000000000000000000000000000000000000000000500000055010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746f6d586d6c223e3c64733a736368656d61526566733e3c64733a736368656d615265662064733a7572693d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f7267
2f6f6666696365446f63756d656e742f323030362f6269626c696f677261706879222f3e3c2f64733a736368656d61526566733e3c2f64733a6461746173746f72654974656d3e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105000000000000}}

View File

@@ -1,7 +1,8 @@
PyQt5>=5.6.0 PyQt5>=5.6.0
Pillow>=5.2.0 Pillow>=5.2.0
psutil>=5.0.0 psutil>=5.0.0
python-slugify>=1.2.1,<8.0.0 python-slugify>=1.2.1
raven>=6.0.0 raven>=6.0.0
# PyQt5-tools # PyQt5-tools
mozjpeg-lossless-optimization mozjpeg-lossless-optimization>=1.1.2
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':
@@ -94,8 +78,9 @@ setuptools.setup(
'PyQt5>=5.6.0', 'PyQt5>=5.6.0',
'Pillow>=5.2.0', 'Pillow>=5.2.0',
'psutil>=5.0.0', 'psutil>=5.0.0',
'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,