mirror of
https://github.com/ciromattia/kcc
synced 2026-04-18 06:58:58 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b63b7af2c | ||
|
|
f74e108a3e | ||
|
|
f088ad732e | ||
|
|
8e5d57364d | ||
|
|
b767d5dc2c | ||
|
|
7228055bca | ||
|
|
8c57fbf318 | ||
|
|
7e94861fa1 | ||
|
|
9992ca4d26 | ||
|
|
f47d1427f0 | ||
|
|
ce8998375c | ||
|
|
8870898a87 | ||
|
|
a017cfd00d | ||
|
|
3f4ef3e21e | ||
|
|
4733c6348b | ||
|
|
5ad23d9629 | ||
|
|
db4eb78963 | ||
|
|
988fc93dc5 | ||
|
|
74fee9346c | ||
|
|
9fcacd7ae6 | ||
|
|
8ac58e361f | ||
|
|
61d6972e22 | ||
|
|
c7c1557e72 | ||
|
|
cb93704e08 | ||
|
|
62c5183609 | ||
|
|
a629f267a1 | ||
|
|
aeec4dd294 | ||
|
|
0d3076465b | ||
|
|
984d44b371 | ||
|
|
1111263893 | ||
|
|
5035c7403e | ||
|
|
067aa68162 | ||
|
|
72d07d53ea | ||
|
|
8c242d45d7 | ||
|
|
c655922a57 | ||
|
|
77e8952f12 | ||
|
|
5b069322a4 | ||
|
|
2444a28127 | ||
|
|
3aad79fc30 | ||
|
|
2dbc13303f | ||
|
|
4c36c7c586 | ||
|
|
65007aec07 | ||
|
|
9429bed91c | ||
|
|
3a3ee15cba | ||
|
|
2394aa3747 | ||
|
|
b57992a754 | ||
|
|
c7a62fdcd6 | ||
|
|
8861299d24 | ||
|
|
636447bb62 | ||
|
|
b23c7744cb | ||
|
|
2398a5b1ac | ||
|
|
2b2ac8ff55 | ||
|
|
5209d9a7b8 | ||
|
|
5336870097 | ||
|
|
4371d14391 | ||
|
|
f96b7cb22b | ||
|
|
4dfd2ea942 | ||
|
|
ba7f4336a5 | ||
|
|
9561b04bec | ||
|
|
2a8f8e9ab4 | ||
|
|
b9cef59912 | ||
|
|
f2ab730691 | ||
|
|
44401583e4 | ||
|
|
28faf524c4 | ||
|
|
2d288f72ea | ||
|
|
fb9b3c676b | ||
|
|
cff1de4fa5 |
@@ -1,13 +1,40 @@
|
|||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
|
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
KindleComicConverter.egg-info
|
KindleComicConverter.egg-info
|
||||||
|
|
||||||
.dockerignore
|
.dockerignore
|
||||||
.gitignore
|
.gitignore
|
||||||
.travis.yml
|
.travis.yml
|
||||||
|
|
||||||
Dockerfile
|
Dockerfile
|
||||||
venv
|
venv
|
||||||
|
.venv
|
||||||
|
__pycache__/
|
||||||
|
*/__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
*.md
|
*.md
|
||||||
LICENSE.txt
|
*.txt
|
||||||
|
!requirements-docker.txt
|
||||||
MANIFEST.in
|
MANIFEST.in
|
||||||
|
|
||||||
|
*.yml
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
*.svg
|
||||||
|
*.jpg
|
||||||
|
*.json
|
||||||
|
|
||||||
|
gen_ui_files.bat
|
||||||
|
gen_ui_files.sh
|
||||||
|
|
||||||
|
gui/
|
||||||
|
icons/
|
||||||
|
|
||||||
|
kindlecomicconverter/KCC_gui.py
|
||||||
|
kindlecomicconverter/KCC_rc.py
|
||||||
|
kindlecomicconverter/KCC_ui_editor.py
|
||||||
|
kindlecomicconverter/KCC_ui.py
|
||||||
|
|||||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|||||||
34
.github/workflows/docker-base-publish.yml
vendored
34
.github/workflows/docker-base-publish.yml
vendored
@@ -1,34 +0,0 @@
|
|||||||
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 }}
|
|
||||||
68
.github/workflows/docker-publish.yml
vendored
68
.github/workflows/docker-publish.yml
vendored
@@ -1,10 +1,10 @@
|
|||||||
name: Docker
|
name: Build and Publish Docker Image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
# Publish semver tags as releases.
|
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:
|
||||||
@@ -15,19 +15,53 @@ on:
|
|||||||
- 'LICENSE'
|
- 'LICENSE'
|
||||||
- '.gitattributes'
|
- '.gitattributes'
|
||||||
- '.gitignore'
|
- '.gitignore'
|
||||||
- '.dockerignore'
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_push:
|
build_and_publish_base_image:
|
||||||
uses: sdr-enthusiasts/common-github-workflows/.github/workflows/build_and_push_image.yml@main
|
runs-on: ubuntu-latest
|
||||||
with:
|
steps:
|
||||||
platform_linux_arm32v7_enabled: true
|
- name: Checkout
|
||||||
platform_linux_arm64v8_enabled: true
|
uses: actions/checkout@v6
|
||||||
platform_linux_amd64_enabled: true
|
|
||||||
push_enabled: true
|
- name: Login to GitHub Container Registry
|
||||||
build_nohealthcheck: false
|
uses: docker/login-action@v3
|
||||||
ghcr_repo_owner: ${{ github.repository_owner }}
|
with:
|
||||||
ghcr_repo: ${{ github.repository }}
|
registry: ghcr.io
|
||||||
secrets:
|
username: ${{ github.repository_owner }}
|
||||||
ghcr_token: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Set Release Date
|
||||||
|
id: release_date
|
||||||
|
run: |
|
||||||
|
echo "release_date=$(date --rfc-3339=date)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository_owner }}/kcc
|
||||||
|
# Always creates the "latest" tag
|
||||||
|
flavor: |
|
||||||
|
latest=true
|
||||||
|
tags: |
|
||||||
|
type=ref,event=tag
|
||||||
|
type=raw,value=${{ steps.release_date.outputs.release_date }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ steps.meta.outputs.tags }}
|
||||||
|
cache-from: |
|
||||||
|
type=registry,ref=ghcr.io/ciromattia/kcc:cache
|
||||||
|
type=registry,ref=ghcr.io/${{ github.repository_owner }}/kcc:cache
|
||||||
|
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/kcc:cache,mode=max
|
||||||
|
|||||||
4
.github/workflows/package-linux.yml
vendored
4
.github/workflows/package-linux.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
UPDATE_INFO: gh-releases-zsync|ciromattia|kcc|latest|*x86_64.AppImage.zsync
|
UPDATE_INFO: gh-releases-zsync|ciromattia|kcc|latest|*x86_64.AppImage.zsync
|
||||||
- name: upload artifact
|
- name: upload artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: AppImage
|
name: AppImage
|
||||||
path: './*.AppImage*'
|
path: './*.AppImage*'
|
||||||
|
|||||||
9
.github/workflows/package-macos.yml
vendored
9
.github/workflows/package-macos.yml
vendored
@@ -25,10 +25,12 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ macos-13, macos-14 ]
|
os: [ macos-15-intel, macos-14 ]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
MACOSX_DEPLOYMENT_TARGET: '14.0'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
@@ -78,7 +80,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python setup.py build_binary
|
python setup.py build_binary
|
||||||
- name: upload build
|
- name: upload build
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: mac-os-build-${{ runner.arch }}
|
name: mac-os-build-${{ runner.arch }}
|
||||||
path: dist/*.dmg
|
path: dist/*.dmg
|
||||||
@@ -89,7 +91,6 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
LICENSE.txt
|
|
||||||
dist/*.dmg
|
dist/*.dmg
|
||||||
- name: Clean up keychain and provisioning profile
|
- name: Clean up keychain and provisioning profile
|
||||||
# TODO signing
|
# TODO signing
|
||||||
|
|||||||
6
.github/workflows/package-osx-legacy.yml
vendored
6
.github/workflows/package-osx-legacy.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ macos-13 ]
|
os: [ macos-15-intel ]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
env:
|
env:
|
||||||
# We need the official Python, because the GA ones only support newer macOS versions
|
# We need the official Python, because the GA ones only support newer macOS versions
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
PYTHON_VERSION: 3.11.9
|
PYTHON_VERSION: 3.11.9
|
||||||
MACOSX_DEPLOYMENT_TARGET: '10.14'
|
MACOSX_DEPLOYMENT_TARGET: '10.14'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Get Python
|
- name: Get Python
|
||||||
run: curl https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg -o "python.pkg"
|
run: curl https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg -o "python.pkg"
|
||||||
- name: Install Python
|
- name: Install Python
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python3 setup.py build_binary
|
python3 setup.py build_binary
|
||||||
- name: upload build
|
- name: upload build
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: osx-build-${{ runner.arch }}
|
name: osx-build-${{ runner.arch }}
|
||||||
path: dist/*.dmg
|
path: dist/*.dmg
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
# 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:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
entry: [ kcc-c2e, kcc-c2p ]
|
|
||||||
include:
|
|
||||||
- entry: kcc-c2e
|
|
||||||
capital: KCC_c2e
|
|
||||||
- entry: kcc-c2p
|
|
||||||
capital: KCC_c2p
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Package Application
|
|
||||||
uses: JackMcKew/pyinstaller-action-windows@main
|
|
||||||
with:
|
|
||||||
path: .
|
|
||||||
spec: ./${{ matrix.entry }}.spec
|
|
||||||
- name: rename binaries
|
|
||||||
run: |
|
|
||||||
version_built=$(cat kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/[^.0-9b]//g")
|
|
||||||
mv dist/windows/${{ matrix.entry }}.exe dist/windows/${{ matrix.capital }}_${version_built}.exe
|
|
||||||
|
|
||||||
- name: upload-unsigned-artifact
|
|
||||||
id: upload-unsigned-artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows-build-${{ matrix.entry }}
|
|
||||||
path: dist/windows/*.exe
|
|
||||||
|
|
||||||
- id: optional_step_id
|
|
||||||
uses: signpath/github-action-submit-signing-request@v1.3
|
|
||||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
|
||||||
with:
|
|
||||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
|
||||||
organization-id: '1dc1bad6-4a8c-4f85-af30-5c5d3d392ea6'
|
|
||||||
project-slug: 'kcc'
|
|
||||||
signing-policy-slug: 'release-signing'
|
|
||||||
github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}'
|
|
||||||
wait-for-completion: true
|
|
||||||
output-artifact-directory: 'dist/windows/'
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
with:
|
|
||||||
prerelease: true
|
|
||||||
generate_release_notes: true
|
|
||||||
files: |
|
|
||||||
LICENSE.txt
|
|
||||||
dist/windows/*.exe
|
|
||||||
21
.github/workflows/package-windows.yml
vendored
21
.github/workflows/package-windows.yml
vendored
@@ -23,9 +23,19 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
entry: [ kcc, kcc-c2e, kcc-c2p ]
|
||||||
|
include:
|
||||||
|
- entry: kcc
|
||||||
|
command: build_binary
|
||||||
|
- entry: kcc-c2e
|
||||||
|
command: build_c2e
|
||||||
|
- entry: kcc-c2p
|
||||||
|
command: build_c2p
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
@@ -40,15 +50,15 @@ jobs:
|
|||||||
pip install certifi pyinstaller --no-binary pyinstaller
|
pip install certifi pyinstaller --no-binary pyinstaller
|
||||||
- name: build binary
|
- name: build binary
|
||||||
run: |
|
run: |
|
||||||
python setup.py build_binary
|
python setup.py ${{ matrix.command }}
|
||||||
- name: upload-unsigned-artifact
|
- name: upload-unsigned-artifact
|
||||||
id: upload-unsigned-artifact
|
id: upload-unsigned-artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: windows-build
|
name: windows-build-${{ matrix.entry }}
|
||||||
path: dist/*.exe
|
path: dist/*.exe
|
||||||
- id: optional_step_id
|
- id: optional_step_id
|
||||||
uses: signpath/github-action-submit-signing-request@v1.3
|
uses: signpath/github-action-submit-signing-request@v2.0
|
||||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||||
with:
|
with:
|
||||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||||
@@ -65,5 +75,4 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
files: |
|
files: |
|
||||||
LICENSE.txt
|
|
||||||
dist/*.exe
|
dist/*.exe
|
||||||
|
|||||||
4
.github/workflows/package-windows7.yml
vendored
4
.github/workflows/package-windows7.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
WINDOWS_7: 1
|
WINDOWS_7: 1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
python setup.py build_binary
|
python setup.py build_binary
|
||||||
- name: upload-unsigned-artifact
|
- name: upload-unsigned-artifact
|
||||||
id: upload-unsigned-artifact
|
id: upload-unsigned-artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: windows7-build
|
name: windows7-build
|
||||||
path: dist/*.exe
|
path: dist/*.exe
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ dist/
|
|||||||
build/
|
build/
|
||||||
KindleComicConverter*.egg-info/
|
KindleComicConverter*.egg-info/
|
||||||
.idea/
|
.idea/
|
||||||
|
.vscode/
|
||||||
win7
|
win7
|
||||||
osx10.11
|
osx10.11
|
||||||
/venv/
|
/venv/
|
||||||
|
|||||||
90
Dockerfile
90
Dockerfile
@@ -1,19 +1,77 @@
|
|||||||
# Select final stage based on TARGETARCH ARG
|
# STAGE 1: BUILDER
|
||||||
FROM ghcr.io/ciromattia/kcc:docker-base-20241116
|
# Contains all build tools and dev dependencies, will be discarded
|
||||||
LABEL com.kcc.name="Kindle Comic Converter"
|
FROM python:3.13-slim-bullseye AS builder
|
||||||
LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi"
|
|
||||||
LABEL org.opencontainers.image.description='Kindle Comic Converter'
|
|
||||||
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"
|
|
||||||
|
|
||||||
COPY . /opt/kcc
|
# Install system dependencies
|
||||||
RUN cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
|
RUN set -x && \
|
||||||
|
BUILD_DEPS="build-essential cmake libffi-dev libfreetype6-dev libfontconfig1-dev libpng-dev libjpeg-dev libssl-dev libxft-dev make python3-dev python3-setuptools python3-wheel" && \
|
||||||
|
RUNTIME_DEPS="bash ca-certificates chrpath locales locales-all libfreetype6 libfontconfig1 p7zip-full python3 python3-pip libgl1" && \
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get update -y && \
|
||||||
|
apt-get install -y --no-install-recommends ${BUILD_DEPS} ${RUNTIME_DEPS}
|
||||||
|
|
||||||
ENTRYPOINT ["/opt/kcc/kcc-c2e.py"]
|
RUN \
|
||||||
|
set -x && \
|
||||||
|
python -m venv /opt/venv && \
|
||||||
|
. /opt/venv/bin/activate && \
|
||||||
|
pip install --upgrade pip
|
||||||
|
|
||||||
|
# Install numpy first, as it is unlikely to change and takes too long to compile
|
||||||
|
RUN \
|
||||||
|
set -x && \
|
||||||
|
. /opt/venv/bin/activate && \
|
||||||
|
pip install --no-cache-dir numpy==2.3.4
|
||||||
|
|
||||||
|
# Install PyMuPDF separately, as it is likely to change but still takes too long to compile
|
||||||
|
RUN \
|
||||||
|
set -x && \
|
||||||
|
. /opt/venv/bin/activate && \
|
||||||
|
pip install --no-cache-dir PyMuPDF==1.26.6
|
||||||
|
|
||||||
|
# Install Python dependencies using virtual environment
|
||||||
|
COPY requirements-docker.txt .
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
set -x && \
|
||||||
|
. /opt/venv/bin/activate && \
|
||||||
|
pip install --no-cache-dir -r requirements-docker.txt
|
||||||
|
|
||||||
|
# STAGE 2: FINAL
|
||||||
|
# Clean, small and secure image with only runtime dependencies
|
||||||
|
FROM python:3.13-slim-bullseye
|
||||||
|
|
||||||
|
# Install runtime dependencies only
|
||||||
|
RUN \
|
||||||
|
set -x && \
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get update -y && \
|
||||||
|
apt-get install -y --no-install-recommends p7zip-full && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy artifacts from builder
|
||||||
|
COPY --from=builder /opt/venv /opt/venv
|
||||||
|
COPY . /opt/kcc/
|
||||||
|
|
||||||
|
WORKDIR /opt/kcc
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
|
||||||
|
# Setup executable and version file
|
||||||
|
RUN \
|
||||||
|
chmod +x /opt/kcc/entrypoint.sh && \
|
||||||
|
ln -s /opt/kcc/kcc-c2e.py /usr/local/bin/c2e && \
|
||||||
|
ln -s /opt/kcc/kcc-c2p.py /usr/local/bin/c2p && \
|
||||||
|
ln -s /opt/kcc/entrypoint.sh /usr/local/bin/entrypoint && \
|
||||||
|
ln -s /opt/kcc/kindlegen/kindlegen /usr/local/bin/kindlegen && \
|
||||||
|
cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
|
||||||
|
|
||||||
|
LABEL com.kcc.name="Kindle Comic Converter" \
|
||||||
|
com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi" \
|
||||||
|
org.opencontainers.image.title="Kindle Comic Converter" \
|
||||||
|
org.opencontainers.image.description='Kindle Comic Converter' \
|
||||||
|
org.opencontainers.image.documentation='https://github.com/ciromattia/kcc' \
|
||||||
|
org.opencontainers.image.source='https://github.com/ciromattia/kcc' \
|
||||||
|
org.opencontainers.image.authors='Darodi and José Cerezo' \
|
||||||
|
org.opencontainers.image.url='https://github.com/ciromattia/kcc' \
|
||||||
|
org.opencontainers.image.vendor='ciromattia' \
|
||||||
|
org.opencontainers.image.licenses='ISC'
|
||||||
|
|
||||||
|
ENTRYPOINT ["entrypoint"]
|
||||||
CMD ["-h"]
|
CMD ["-h"]
|
||||||
|
|||||||
164
Dockerfile-base
164
Dockerfile-base
@@ -1,164 +0,0 @@
|
|||||||
FROM --platform=linux/amd64 python:3.13-slim-bullseye as compile-amd64
|
|
||||||
ARG TARGETOS
|
|
||||||
ARG TARGETARCH
|
|
||||||
ARG TARGETVARIANT
|
|
||||||
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
|
|
||||||
|
|
||||||
|
|
||||||
COPY requirements.txt /opt/kcc/
|
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
|
||||||
apt-get install -y libpng-dev libjpeg-dev p7zip-full unrar-free libgl1 && \
|
|
||||||
python -m pip install --upgrade pip && \
|
|
||||||
python -m venv /opt/venv && \
|
|
||||||
python -m pip install -r /opt/kcc/requirements.txt
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################################
|
|
||||||
|
|
||||||
FROM --platform=linux/arm64 python:3.13-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"]
|
|
||||||
|
|
||||||
COPY requirements.txt /opt/kcc/
|
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
|
||||||
|
|
||||||
RUN set -x && \
|
|
||||||
TEMP_PACKAGES=() && \
|
|
||||||
KEPT_PACKAGES=() && \
|
|
||||||
# Packages only required during build
|
|
||||||
TEMP_PACKAGES+=(build-essential) && \
|
|
||||||
TEMP_PACKAGES+=(cmake) && \
|
|
||||||
TEMP_PACKAGES+=(libfreetype6-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libfontconfig1-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libpng-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libjpeg-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libssl-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libxft-dev) && \
|
|
||||||
TEMP_PACKAGES+=(make) && \
|
|
||||||
TEMP_PACKAGES+=(python3-dev) && \
|
|
||||||
TEMP_PACKAGES+=(python3-setuptools) && \
|
|
||||||
TEMP_PACKAGES+=(python3-wheel) && \
|
|
||||||
# Packages kept in the image
|
|
||||||
KEPT_PACKAGES+=(bash) && \
|
|
||||||
KEPT_PACKAGES+=(ca-certificates) && \
|
|
||||||
KEPT_PACKAGES+=(chrpath) && \
|
|
||||||
KEPT_PACKAGES+=(locales) && \
|
|
||||||
KEPT_PACKAGES+=(locales-all) && \
|
|
||||||
KEPT_PACKAGES+=(libfreetype6) && \
|
|
||||||
KEPT_PACKAGES+=(libfontconfig1) && \
|
|
||||||
KEPT_PACKAGES+=(p7zip-full) && \
|
|
||||||
KEPT_PACKAGES+=(python3) && \
|
|
||||||
KEPT_PACKAGES+=(python3-pip) && \
|
|
||||||
KEPT_PACKAGES+=(unrar-free) && \
|
|
||||||
# Install packages
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
${KEPT_PACKAGES[@]} \
|
|
||||||
${TEMP_PACKAGES[@]} \
|
|
||||||
&& \
|
|
||||||
# Install required python modules
|
|
||||||
python -m pip install --upgrade pip && \
|
|
||||||
python -m venv /opt/venv && \
|
|
||||||
python -m pip install -r /opt/kcc/requirements.txt
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################################
|
|
||||||
|
|
||||||
FROM --platform=linux/arm/v7 python:3.13-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"]
|
|
||||||
|
|
||||||
COPY requirements.txt /opt/kcc/
|
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
|
||||||
|
|
||||||
RUN set -x && \
|
|
||||||
TEMP_PACKAGES=() && \
|
|
||||||
KEPT_PACKAGES=() && \
|
|
||||||
# Packages only required during build
|
|
||||||
TEMP_PACKAGES+=(build-essential) && \
|
|
||||||
TEMP_PACKAGES+=(cmake) && \
|
|
||||||
TEMP_PACKAGES+=(libffi-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libfreetype6-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libfontconfig1-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libpng-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libjpeg-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libssl-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libxft-dev) && \
|
|
||||||
TEMP_PACKAGES+=(make) && \
|
|
||||||
TEMP_PACKAGES+=(python3-dev) && \
|
|
||||||
TEMP_PACKAGES+=(python3-setuptools) && \
|
|
||||||
TEMP_PACKAGES+=(python3-wheel) && \
|
|
||||||
# Packages kept in the image
|
|
||||||
KEPT_PACKAGES+=(bash) && \
|
|
||||||
KEPT_PACKAGES+=(ca-certificates) && \
|
|
||||||
KEPT_PACKAGES+=(chrpath) && \
|
|
||||||
KEPT_PACKAGES+=(locales) && \
|
|
||||||
KEPT_PACKAGES+=(locales-all) && \
|
|
||||||
KEPT_PACKAGES+=(libfreetype6) && \
|
|
||||||
KEPT_PACKAGES+=(libfontconfig1) && \
|
|
||||||
KEPT_PACKAGES+=(p7zip-full) && \
|
|
||||||
KEPT_PACKAGES+=(python3) && \
|
|
||||||
KEPT_PACKAGES+=(python3-pip) && \
|
|
||||||
KEPT_PACKAGES+=(unrar-free) && \
|
|
||||||
# Install packages
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
${KEPT_PACKAGES[@]} \
|
|
||||||
${TEMP_PACKAGES[@]} \
|
|
||||||
&& \
|
|
||||||
# Install required python modules
|
|
||||||
python -m pip install --upgrade pip && \
|
|
||||||
python -m venv /opt/venv && \
|
|
||||||
python -m pip install --upgrade pillow psutil requests python-slugify raven packaging mozjpeg-lossless-optimization natsort distro numpy pymupdf
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################################
|
|
||||||
FROM --platform=linux/amd64 python:3.13-slim-bullseye as build-amd64
|
|
||||||
COPY --from=compile-amd64 /opt/venv /opt/venv
|
|
||||||
|
|
||||||
FROM --platform=linux/arm64 python:3.13-slim-bullseye as build-arm64
|
|
||||||
COPY --from=compile-arm64 /opt/venv /opt/venv
|
|
||||||
|
|
||||||
FROM --platform=linux/arm/v7 python:3.13-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-20241116 > /IMAGE_VERSION
|
|
||||||
|
|
||||||
12
README.md
12
README.md
@@ -7,7 +7,7 @@
|
|||||||
[](https://github.com/ciromattia/kcc/releases)
|
[](https://github.com/ciromattia/kcc/releases)
|
||||||
|
|
||||||
|
|
||||||
**Kindle Comic Converter** optimizes black & white comics and manga for E-ink ereaders
|
**Kindle Comic Converter** optimizes black & white (or color) comics and manga for E-ink ereaders
|
||||||
like Kindle, Kobo, ReMarkable, and more.
|
like Kindle, Kobo, ReMarkable, and more.
|
||||||
Pages display in fullscreen without margins,
|
Pages display in fullscreen without margins,
|
||||||
with proper fixed layout support.
|
with proper fixed layout support.
|
||||||
@@ -26,7 +26,7 @@ which have different requirements than normal LCD screens.
|
|||||||
Combining that with downscaling to your specific device's screen resolution
|
Combining that with downscaling to your specific device's screen resolution
|
||||||
can result in filesize reductions of hundreds of MB per volume with no visible quality loss on eink.
|
can result in filesize reductions of hundreds of MB per volume with no visible quality loss on eink.
|
||||||
This can also improve battery life, page turn speed, and general performance
|
This can also improve battery life, page turn speed, and general performance
|
||||||
on underpowered ereaders with small storage capacities.
|
on underpowered ereaders with small memory and storage capacities.
|
||||||
|
|
||||||
KCC avoids many common formatting issues (some of which occur [even on the Kindle Store](https://github.com/ciromattia/kcc/wiki/Kindle-Store-bad-formatting)), such as:
|
KCC avoids many common formatting issues (some of which occur [even on the Kindle Store](https://github.com/ciromattia/kcc/wiki/Kindle-Store-bad-formatting)), such as:
|
||||||
1) faded black levels causing unneccessarily low contrast, which is hard to see and can cause eyestrain.
|
1) faded black levels causing unneccessarily low contrast, which is hard to see and can cause eyestrain.
|
||||||
@@ -92,7 +92,7 @@ Click on **Assets** of the latest release.
|
|||||||
You probably want either
|
You probably want either
|
||||||
- `KCC_*.*.*.exe` (Windows)
|
- `KCC_*.*.*.exe` (Windows)
|
||||||
- `kcc_macos_arm_*.*.*.dmg` (recent Mac with Apple Silicon M1 chip or later)
|
- `kcc_macos_arm_*.*.*.dmg` (recent Mac with Apple Silicon M1 chip or later)
|
||||||
- `kcc_macos_i386_*.*.*.dmg` (older Mac with Intel chip macOS 12+)
|
- `kcc_macos_i386_*.*.*.dmg` (older Mac with Intel chip macOS 14+)
|
||||||
|
|
||||||
There are also legacy macOS 10.14+ and Windows 7 experimental versions available.
|
There are also legacy macOS 10.14+ and Windows 7 experimental versions available.
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
|
|||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
- Should I use Calibre?
|
- Should I use Calibre?
|
||||||
- No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output (even just metadata!) in Calibre will break the formatting.
|
- No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output (even just metadata!) in Calibre can break the formatting.
|
||||||
Viewing KCC output in Calibre will also not work properly.
|
Viewing KCC output in Calibre will also not work properly.
|
||||||
On 7th gen and later Kindles running firmware 5.15.1+, you can get cover thumbnails simply by USB dropping into documents folder.
|
On 7th gen and later Kindles running firmware 5.15.1+, you can get cover thumbnails simply by USB dropping into documents folder.
|
||||||
On 6th gen and older, you can get cover thumbnails by keeping Kindle plugged in during conversion.
|
On 6th gen and older, you can get cover thumbnails by keeping Kindle plugged in during conversion.
|
||||||
@@ -116,6 +116,8 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
|
|||||||
- MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable.
|
- MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable.
|
||||||
- All options have additional information in tooltips if you hover over the option.
|
- All options have additional information in tooltips if you hover over the option.
|
||||||
- To get the converted book onto your Kindle/Kobo, just drag and drop the mobi/kepub into the documents folder on your Kindle/Kobo via USB
|
- To get the converted book onto your Kindle/Kobo, just drag and drop the mobi/kepub into the documents folder on your Kindle/Kobo via USB
|
||||||
|
- Kindle panel view not working?
|
||||||
|
- Virtual panel view is enabled in Aa menu on your Kindle, not in KCC as of 7.4
|
||||||
- Right to left mode not working?
|
- Right to left mode not working?
|
||||||
- RTL mode only affects splitting order for CBZ output. Your cbz reader itself sets the page turn direction.
|
- RTL mode only affects splitting order for CBZ output. Your cbz reader itself sets the page turn direction.
|
||||||
- Colors inverted?
|
- Colors inverted?
|
||||||
@@ -272,6 +274,7 @@ OUTPUT SETTINGS:
|
|||||||
--spreadshift Shift first page to opposite side in landscape for two page spread alignment
|
--spreadshift Shift first page to opposite side in landscape for two page spread alignment
|
||||||
--norotate Do not rotate double page spreads in spread splitter option.
|
--norotate Do not rotate double page spreads in spread splitter option.
|
||||||
--rotatefirst Put rotated spread first in spread splitter option.
|
--rotatefirst Put rotated spread first in spread splitter option.
|
||||||
|
--filefusion Combines all input files into a single file.
|
||||||
--eraserainbow Erase rainbow effect on color eink screen by attenuating interfering frequencies
|
--eraserainbow Erase rainbow effect on color eink screen by attenuating interfering frequencies
|
||||||
|
|
||||||
CUSTOM PROFILE:
|
CUSTOM PROFILE:
|
||||||
@@ -315,6 +318,7 @@ Depending on your system [Python](https://www.python.org) may be called either `
|
|||||||
If you want to edit the code, a good code editor is [VS Code](https://code.visualstudio.com).
|
If you want to edit the code, a good code editor is [VS Code](https://code.visualstudio.com).
|
||||||
|
|
||||||
If you want to edit the `.ui` files, use `pyside6-designer` which is included in the `pip install pyside6`.
|
If you want to edit the `.ui` files, use `pyside6-designer` which is included in the `pip install pyside6`.
|
||||||
|
If new objects have been added, verify that correct tab order has been applied by using [Tab Order Editing Mode](https://doc.qt.io/qt-6/designer-tab-order.html).
|
||||||
Then use the `gen_ui_files` scripts to autogenerate the python UI.
|
Then use the `gen_ui_files` scripts to autogenerate the python UI.
|
||||||
|
|
||||||
An example PR adding a new checkbox is here: https://github.com/ciromattia/kcc/pull/785
|
An example PR adding a new checkbox is here: https://github.com/ciromattia/kcc/pull/785
|
||||||
|
|||||||
22
entrypoint.sh
Normal file
22
entrypoint.sh
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
MODE=${KCC_MODE:-c2e}
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
"c2e")
|
||||||
|
echo "Starting C2E..."
|
||||||
|
exec c2e "$@"
|
||||||
|
;;
|
||||||
|
|
||||||
|
"c2p")
|
||||||
|
echo "Starting C2P..."
|
||||||
|
exec c2p "$@"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Error: Unknown mode '$MODE'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
name: kcc
|
|
||||||
channels:
|
|
||||||
- conda-forge
|
|
||||||
- defaults
|
|
||||||
dependencies:
|
|
||||||
- python=3.11
|
|
||||||
- Pillow>=11.3.0
|
|
||||||
- psutil>=5.9.5
|
|
||||||
- python-slugify>=1.2.1
|
|
||||||
- raven>=6.0.0
|
|
||||||
- distro
|
|
||||||
- natsort>=8.4.0
|
|
||||||
- pip
|
|
||||||
- pip:
|
|
||||||
- mozjpeg-lossless-optimization>=1.1.2
|
|
||||||
- pyside6>=6.5.1
|
|
||||||
123
gui/KCC.ui
123
gui/KCC.ui
@@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>566</width>
|
<width>566</width>
|
||||||
<height>573</height>
|
<height>581</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
<string><html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Right-to-left mode</string>
|
<string>Right-to-left (manga)</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -348,6 +348,51 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QWidget" name="outputFolderWidget" native="true">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="defaultOutputFolderBox">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - next to source<br/></span>Place output files next to source files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - folder next to source<br/></span>Place output files in a folder next to source files</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Custom<br/></span>Place output files in custom directory specified by right button</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Output Folder</string>
|
||||||
|
</property>
|
||||||
|
<property name="tristate">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="defaultOutputFolderButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>30</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Use this to select the default output directory.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="KCC.qrc">
|
||||||
|
<normaloff>:/Other/icons/folder_new.png</normaloff>:/Other/icons/folder_new.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -416,7 +461,7 @@
|
|||||||
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>3200</number>
|
<number>6000</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -442,7 +487,7 @@
|
|||||||
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>5120</number>
|
<number>8000</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -747,7 +792,7 @@
|
|||||||
<string><html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Add file(s)</string>
|
<string>Add input file(s)</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="KCC.qrc">
|
<iconset resource="KCC.qrc">
|
||||||
@@ -755,19 +800,19 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="5">
|
<item row="0" column="4">
|
||||||
<widget class="QPushButton" name="defaultOutputFolderButton">
|
<widget class="QPushButton" name="directoryButton">
|
||||||
<property name="minimumSize">
|
<property name="sizePolicy">
|
||||||
<size>
|
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||||
<width>0</width>
|
<horstretch>0</horstretch>
|
||||||
<height>30</height>
|
<verstretch>0</verstretch>
|
||||||
</size>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>Use this to select the default output directory.</p></body></html></string>
|
<string><html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=" font-weight:600;">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string/>
|
<string>Add input folder(s)</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset resource="KCC.qrc">
|
<iconset resource="KCC.qrc">
|
||||||
@@ -775,26 +820,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="4">
|
<item row="1" column="4">
|
||||||
<widget class="QCheckBox" name="defaultOutputFolderBox">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - next to source<br/></span>Place output files next to source files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - folder next to source<br/></span>Place output files in a folder next to source files</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Custom<br/></span>Place output files in custom directory specified by right button</p></body></html></string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Output Folder</string>
|
|
||||||
</property>
|
|
||||||
<property name="tristate">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="4" colspan="2">
|
|
||||||
<widget class="QComboBox" name="formatBox">
|
<widget class="QComboBox" name="formatBox">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
@@ -811,10 +837,9 @@
|
|||||||
<zorder>clearButton</zorder>
|
<zorder>clearButton</zorder>
|
||||||
<zorder>deviceBox</zorder>
|
<zorder>deviceBox</zorder>
|
||||||
<zorder>convertButton</zorder>
|
<zorder>convertButton</zorder>
|
||||||
<zorder>formatBox</zorder>
|
|
||||||
<zorder>defaultOutputFolderButton</zorder>
|
|
||||||
<zorder>fileButton</zorder>
|
<zorder>fileButton</zorder>
|
||||||
<zorder>defaultOutputFolderBox</zorder>
|
<zorder>directoryButton</zorder>
|
||||||
|
<zorder>formatBox</zorder>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0">
|
<item row="6" column="0">
|
||||||
@@ -898,36 +923,44 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>convertButton</tabstop>
|
<tabstop>jobList</tabstop>
|
||||||
|
<tabstop>fileButton</tabstop>
|
||||||
<tabstop>clearButton</tabstop>
|
<tabstop>clearButton</tabstop>
|
||||||
<tabstop>deviceBox</tabstop>
|
<tabstop>deviceBox</tabstop>
|
||||||
<tabstop>formatBox</tabstop>
|
<tabstop>widthBox</tabstop>
|
||||||
|
<tabstop>heightBox</tabstop>
|
||||||
|
<tabstop>convertButton</tabstop>
|
||||||
<tabstop>mangaBox</tabstop>
|
<tabstop>mangaBox</tabstop>
|
||||||
<tabstop>rotateBox</tabstop>
|
<tabstop>rotateBox</tabstop>
|
||||||
<tabstop>qualityBox</tabstop>
|
<tabstop>qualityBox</tabstop>
|
||||||
<tabstop>webtoonBox</tabstop>
|
<tabstop>webtoonBox</tabstop>
|
||||||
<tabstop>upscaleBox</tabstop>
|
<tabstop>upscaleBox</tabstop>
|
||||||
<tabstop>gammaBox</tabstop>
|
<tabstop>gammaBox</tabstop>
|
||||||
|
<tabstop>gammaSlider</tabstop>
|
||||||
<tabstop>borderBox</tabstop>
|
<tabstop>borderBox</tabstop>
|
||||||
<tabstop>outputSplit</tabstop>
|
<tabstop>outputSplit</tabstop>
|
||||||
<tabstop>colorBox</tabstop>
|
<tabstop>colorBox</tabstop>
|
||||||
<tabstop>mozJpegBox</tabstop>
|
<tabstop>mozJpegBox</tabstop>
|
||||||
<tabstop>maximizeStrips</tabstop>
|
<tabstop>maximizeStrips</tabstop>
|
||||||
<tabstop>croppingBox</tabstop>
|
<tabstop>croppingBox</tabstop>
|
||||||
|
<tabstop>croppingPowerSlider</tabstop>
|
||||||
|
<tabstop>preserveMarginBox</tabstop>
|
||||||
<tabstop>spreadShiftBox</tabstop>
|
<tabstop>spreadShiftBox</tabstop>
|
||||||
<tabstop>deleteBox</tabstop>
|
<tabstop>deleteBox</tabstop>
|
||||||
<tabstop>disableProcessingBox</tabstop>
|
<tabstop>disableProcessingBox</tabstop>
|
||||||
<tabstop>chunkSizeBox</tabstop>
|
<tabstop>fileFusionBox</tabstop>
|
||||||
<tabstop>noRotateBox</tabstop>
|
<tabstop>noRotateBox</tabstop>
|
||||||
<tabstop>interPanelCropBox</tabstop>
|
<tabstop>interPanelCropBox</tabstop>
|
||||||
|
<tabstop>metadataTitleBox</tabstop>
|
||||||
|
<tabstop>chunkSizeCheckBox</tabstop>
|
||||||
|
<tabstop>chunkSizeBox</tabstop>
|
||||||
<tabstop>eraseRainbowBox</tabstop>
|
<tabstop>eraseRainbowBox</tabstop>
|
||||||
<tabstop>heightBox</tabstop>
|
<tabstop>rotateFirstBox</tabstop>
|
||||||
<tabstop>croppingPowerSlider</tabstop>
|
<tabstop>autoLevelBox</tabstop>
|
||||||
|
<tabstop>autocontrastBox</tabstop>
|
||||||
<tabstop>editorButton</tabstop>
|
<tabstop>editorButton</tabstop>
|
||||||
|
<tabstop>kofiButton</tabstop>
|
||||||
<tabstop>wikiButton</tabstop>
|
<tabstop>wikiButton</tabstop>
|
||||||
<tabstop>jobList</tabstop>
|
|
||||||
<tabstop>gammaSlider</tabstop>
|
|
||||||
<tabstop>widthBox</tabstop>
|
|
||||||
</tabstops>
|
</tabstops>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="KCC.qrc"/>
|
<include location="KCC.qrc"/>
|
||||||
|
|||||||
@@ -192,6 +192,18 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>seriesLine</tabstop>
|
||||||
|
<tabstop>volumeLine</tabstop>
|
||||||
|
<tabstop>titleLine</tabstop>
|
||||||
|
<tabstop>numberLine</tabstop>
|
||||||
|
<tabstop>writerLine</tabstop>
|
||||||
|
<tabstop>pencillerLine</tabstop>
|
||||||
|
<tabstop>inkerLine</tabstop>
|
||||||
|
<tabstop>coloristLine</tabstop>
|
||||||
|
<tabstop>okButton</tabstop>
|
||||||
|
<tabstop>cancelButton</tabstop>
|
||||||
|
</tabstops>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="KCC.qrc"/>
|
<include location="KCC.qrc"/>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import itertools
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from PySide6.QtCore import (QSize, QUrl, Qt, Signal, QIODeviceBase, QEvent, QThread, QSettings)
|
from PySide6.QtCore import (QSize, QUrl, Qt, Signal, QIODeviceBase, QEvent, QThread, QSettings)
|
||||||
from PySide6.QtGui import (QColor, QIcon, QPixmap, QDesktopServices)
|
from PySide6.QtGui import (QColor, QIcon, QPixmap, QDesktopServices)
|
||||||
from PySide6.QtWidgets import (QApplication, QLabel, QListWidgetItem, QMainWindow, QSystemTrayIcon, QFileDialog, QMessageBox, QDialog)
|
from PySide6.QtWidgets import (QApplication, QLabel, QListWidgetItem, QMainWindow, QSystemTrayIcon, QFileDialog, QMessageBox, QDialog, QTreeView, QAbstractItemView)
|
||||||
from PySide6.QtNetwork import (QLocalSocket, QLocalServer)
|
from PySide6.QtNetwork import (QLocalSocket, QLocalServer)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -382,13 +382,14 @@ class WorkerThread(QThread):
|
|||||||
error_message = 'Process Failed. Custom title can\'t be set when processing more than 1 source.\nDid you forget to check fusion?'
|
error_message = 'Process Failed. Custom title can\'t be set when processing more than 1 source.\nDid you forget to check fusion?'
|
||||||
print(error_message)
|
print(error_message)
|
||||||
MW.addMessage.emit(error_message, 'error', True)
|
MW.addMessage.emit(error_message, 'error', True)
|
||||||
for job in currentJobs:
|
for i, job in enumerate(currentJobs, start=1):
|
||||||
|
job_progress_number = f'[{i}/{len(currentJobs)}] '
|
||||||
sleep(0.5)
|
sleep(0.5)
|
||||||
if not self.conversionAlive:
|
if not self.conversionAlive:
|
||||||
self.clean()
|
self.clean()
|
||||||
return
|
return
|
||||||
self.errors = False
|
self.errors = False
|
||||||
MW.addMessage.emit('<b>Source:</b> ' + job, 'info', False)
|
MW.addMessage.emit(f'<b>{job_progress_number}Source:</b> ' + job, 'info', False)
|
||||||
if gui_current_format == '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'
|
||||||
@@ -402,7 +403,7 @@ class WorkerThread(QThread):
|
|||||||
jobargv.append(job)
|
jobargv.append(job)
|
||||||
try:
|
try:
|
||||||
comic2ebook.options = comic2ebook.checkOptions(copy(options))
|
comic2ebook.options = comic2ebook.checkOptions(copy(options))
|
||||||
outputPath = comic2ebook.makeBook(job, self)
|
outputPath = comic2ebook.makeBook(job, self, job_progress_number)
|
||||||
MW.hideProgressBar.emit()
|
MW.hideProgressBar.emit()
|
||||||
except UserWarning as warn:
|
except UserWarning as warn:
|
||||||
if not self.conversionAlive:
|
if not self.conversionAlive:
|
||||||
@@ -444,7 +445,7 @@ class WorkerThread(QThread):
|
|||||||
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 'MOBI' in gui_current_format:
|
if 'MOBI' in gui_current_format:
|
||||||
MW.progressBarTick.emit('Creating MOBI files')
|
MW.progressBarTick.emit(f'{job_progress_number}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')
|
||||||
MW.addMessage.emit('Creating MOBI files', 'info', False)
|
MW.addMessage.emit('Creating MOBI files', 'info', False)
|
||||||
@@ -522,7 +523,7 @@ class WorkerThread(QThread):
|
|||||||
if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '':
|
if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '':
|
||||||
MW.showDialog.emit("KindleGen error:\n\n" + self.kindlegenErrorCode[1], 'error')
|
MW.showDialog.emit("KindleGen error:\n\n" + self.kindlegenErrorCode[1], 'error')
|
||||||
if self.kindlegenErrorCode[0] == 23026:
|
if self.kindlegenErrorCode[0] == 23026:
|
||||||
MW.addMessage.emit('Created EPUB file was too big.', 'error', False)
|
MW.addMessage.emit('Created EPUB file was too big. Weird file structure?', 'error', False)
|
||||||
MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error',
|
MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error',
|
||||||
False)
|
False)
|
||||||
if self.kindlegenErrorCode[0] == 3221226505:
|
if self.kindlegenErrorCode[0] == 3221226505:
|
||||||
@@ -609,12 +610,30 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
'Comic (*.pdf);;All (*.*)')
|
'Comic (*.pdf);;All (*.*)')
|
||||||
for fname in fnames[0]:
|
for fname in fnames[0]:
|
||||||
if fname != '':
|
if fname != '':
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
fname = fname.replace('/', '\\')
|
|
||||||
self.lastPath = os.path.abspath(os.path.join(fname, os.pardir))
|
self.lastPath = os.path.abspath(os.path.join(fname, os.pardir))
|
||||||
GUI.jobList.addItem(fname)
|
GUI.jobList.addItem(fname)
|
||||||
GUI.jobList.scrollToBottom()
|
GUI.jobList.scrollToBottom()
|
||||||
|
|
||||||
|
def selectDir(self):
|
||||||
|
if self.needClean:
|
||||||
|
self.needClean = False
|
||||||
|
GUI.jobList.clear()
|
||||||
|
|
||||||
|
dialog = QFileDialog(MW, 'Select input folder(s)', self.lastPath)
|
||||||
|
dialog.setFileMode(QFileDialog.FileMode.Directory)
|
||||||
|
dialog.setOption(QFileDialog.Option.ShowDirsOnly, True)
|
||||||
|
dialog.setOption(QFileDialog.Option.DontUseNativeDialog, True)
|
||||||
|
dialog.findChild(QTreeView).setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||||
|
|
||||||
|
if dialog.exec():
|
||||||
|
dnames = dialog.selectedFiles()
|
||||||
|
for dname in dnames:
|
||||||
|
if dname != '':
|
||||||
|
self.lastPath = os.path.abspath(os.path.join(dname, os.pardir))
|
||||||
|
GUI.jobList.addItem(dname)
|
||||||
|
GUI.jobList.scrollToBottom()
|
||||||
|
|
||||||
|
|
||||||
def selectFileMetaEditor(self, sname):
|
def selectFileMetaEditor(self, sname):
|
||||||
if not sname:
|
if not sname:
|
||||||
if QApplication.keyboardModifiers() == Qt.ShiftModifier:
|
if QApplication.keyboardModifiers() == Qt.ShiftModifier:
|
||||||
@@ -876,6 +895,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
GUI.chunkSizeCheckBox.setChecked(False)
|
GUI.chunkSizeCheckBox.setChecked(False)
|
||||||
elif not GUI.webtoonBox.isChecked():
|
elif not GUI.webtoonBox.isChecked():
|
||||||
GUI.chunkSizeCheckBox.setEnabled(True)
|
GUI.chunkSizeCheckBox.setEnabled(True)
|
||||||
|
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'PDF') and not GUI.webtoonBox.isChecked():
|
||||||
|
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
|
||||||
|
|
||||||
def stripTags(self, html):
|
def stripTags(self, html):
|
||||||
s = HTMLStripper()
|
s = HTMLStripper()
|
||||||
@@ -1025,7 +1046,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
MW.activateWindow()
|
MW.activateWindow()
|
||||||
if type(message) is bytes:
|
if type(message) is bytes:
|
||||||
message = message.decode('UTF-8')
|
message = message.decode('UTF-8')
|
||||||
if not self.conversionAlive and message != 'ARISE':
|
if not self.conversionAlive and message != 'ARISE' and not GUI.jobList.findItems(message, Qt.MatchFlag.MatchExactly):
|
||||||
if self.needClean:
|
if self.needClean:
|
||||||
self.needClean = False
|
self.needClean = False
|
||||||
GUI.jobList.clear()
|
GUI.jobList.clear()
|
||||||
@@ -1056,6 +1077,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
if message[-1] == '/':
|
if message[-1] == '/':
|
||||||
message = message[:-1]
|
message = message[:-1]
|
||||||
self.handleMessage(message)
|
self.handleMessage(message)
|
||||||
|
GUI.jobList.sortItems()
|
||||||
|
|
||||||
def forceShutdown(self):
|
def forceShutdown(self):
|
||||||
self.saveSettings(None)
|
self.saveSettings(None)
|
||||||
@@ -1315,6 +1337,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
GUI.defaultOutputFolderButton.clicked.connect(self.selectDefaultOutputFolder)
|
GUI.defaultOutputFolderButton.clicked.connect(self.selectDefaultOutputFolder)
|
||||||
GUI.clearButton.clicked.connect(self.clearJobs)
|
GUI.clearButton.clicked.connect(self.clearJobs)
|
||||||
GUI.fileButton.clicked.connect(self.selectFile)
|
GUI.fileButton.clicked.connect(self.selectFile)
|
||||||
|
GUI.directoryButton.clicked.connect(self.selectDir)
|
||||||
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
|
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
|
||||||
GUI.wikiButton.clicked.connect(self.openWiki)
|
GUI.wikiButton.clicked.connect(self.openWiki)
|
||||||
GUI.kofiButton.clicked.connect(self.openKofi)
|
GUI.kofiButton.clicked.connect(self.openKofi)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class Ui_mainWindow(object):
|
|||||||
def setupUi(self, mainWindow):
|
def setupUi(self, mainWindow):
|
||||||
if not mainWindow.objectName():
|
if not mainWindow.objectName():
|
||||||
mainWindow.setObjectName(u"mainWindow")
|
mainWindow.setObjectName(u"mainWindow")
|
||||||
mainWindow.resize(566, 573)
|
mainWindow.resize(566, 581)
|
||||||
icon = QIcon()
|
icon = QIcon()
|
||||||
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
mainWindow.setWindowIcon(icon)
|
mainWindow.setWindowIcon(icon)
|
||||||
@@ -190,6 +190,33 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1)
|
self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1)
|
||||||
|
|
||||||
|
self.outputFolderWidget = QWidget(self.optionWidget)
|
||||||
|
self.outputFolderWidget.setObjectName(u"outputFolderWidget")
|
||||||
|
self.horizontalLayout_3 = QHBoxLayout(self.outputFolderWidget)
|
||||||
|
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
|
||||||
|
self.defaultOutputFolderBox = QCheckBox(self.outputFolderWidget)
|
||||||
|
self.defaultOutputFolderBox.setObjectName(u"defaultOutputFolderBox")
|
||||||
|
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||||
|
sizePolicy1.setHorizontalStretch(0)
|
||||||
|
sizePolicy1.setVerticalStretch(0)
|
||||||
|
sizePolicy1.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth())
|
||||||
|
self.defaultOutputFolderBox.setSizePolicy(sizePolicy1)
|
||||||
|
self.defaultOutputFolderBox.setTristate(True)
|
||||||
|
|
||||||
|
self.horizontalLayout_3.addWidget(self.defaultOutputFolderBox)
|
||||||
|
|
||||||
|
self.defaultOutputFolderButton = QPushButton(self.outputFolderWidget)
|
||||||
|
self.defaultOutputFolderButton.setObjectName(u"defaultOutputFolderButton")
|
||||||
|
self.defaultOutputFolderButton.setMinimumSize(QSize(0, 30))
|
||||||
|
icon1 = QIcon()
|
||||||
|
icon1.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
|
self.defaultOutputFolderButton.setIcon(icon1)
|
||||||
|
|
||||||
|
self.horizontalLayout_3.addWidget(self.defaultOutputFolderButton)
|
||||||
|
|
||||||
|
|
||||||
|
self.gridLayout_2.addWidget(self.outputFolderWidget, 0, 2, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
|
||||||
|
|
||||||
@@ -211,30 +238,30 @@ class Ui_mainWindow(object):
|
|||||||
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
|
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
|
||||||
self.hLabel = QLabel(self.customWidget)
|
self.hLabel = QLabel(self.customWidget)
|
||||||
self.hLabel.setObjectName(u"hLabel")
|
self.hLabel.setObjectName(u"hLabel")
|
||||||
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
||||||
sizePolicy1.setHorizontalStretch(0)
|
sizePolicy2.setHorizontalStretch(0)
|
||||||
sizePolicy1.setVerticalStretch(0)
|
sizePolicy2.setVerticalStretch(0)
|
||||||
sizePolicy1.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
|
sizePolicy2.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
|
||||||
self.hLabel.setSizePolicy(sizePolicy1)
|
self.hLabel.setSizePolicy(sizePolicy2)
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
|
self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
|
||||||
|
|
||||||
self.widthBox = QSpinBox(self.customWidget)
|
self.widthBox = QSpinBox(self.customWidget)
|
||||||
self.widthBox.setObjectName(u"widthBox")
|
self.widthBox.setObjectName(u"widthBox")
|
||||||
self.widthBox.setMaximum(3200)
|
self.widthBox.setMaximum(6000)
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
|
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
|
||||||
|
|
||||||
self.wLabel = QLabel(self.customWidget)
|
self.wLabel = QLabel(self.customWidget)
|
||||||
self.wLabel.setObjectName(u"wLabel")
|
self.wLabel.setObjectName(u"wLabel")
|
||||||
sizePolicy1.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
|
sizePolicy2.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
|
||||||
self.wLabel.setSizePolicy(sizePolicy1)
|
self.wLabel.setSizePolicy(sizePolicy2)
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
|
self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
|
||||||
|
|
||||||
self.heightBox = QSpinBox(self.customWidget)
|
self.heightBox = QSpinBox(self.customWidget)
|
||||||
self.heightBox.setObjectName(u"heightBox")
|
self.heightBox.setObjectName(u"heightBox")
|
||||||
self.heightBox.setMaximum(5120)
|
self.heightBox.setMaximum(8000)
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
|
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
|
||||||
|
|
||||||
@@ -271,18 +298,18 @@ class Ui_mainWindow(object):
|
|||||||
self.editorButton = QPushButton(self.toolWidget)
|
self.editorButton = QPushButton(self.toolWidget)
|
||||||
self.editorButton.setObjectName(u"editorButton")
|
self.editorButton.setObjectName(u"editorButton")
|
||||||
self.editorButton.setMinimumSize(QSize(0, 30))
|
self.editorButton.setMinimumSize(QSize(0, 30))
|
||||||
icon1 = QIcon()
|
icon2 = QIcon()
|
||||||
icon1.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon2.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.editorButton.setIcon(icon1)
|
self.editorButton.setIcon(icon2)
|
||||||
|
|
||||||
self.horizontalLayout.addWidget(self.editorButton)
|
self.horizontalLayout.addWidget(self.editorButton)
|
||||||
|
|
||||||
self.kofiButton = QPushButton(self.toolWidget)
|
self.kofiButton = QPushButton(self.toolWidget)
|
||||||
self.kofiButton.setObjectName(u"kofiButton")
|
self.kofiButton.setObjectName(u"kofiButton")
|
||||||
self.kofiButton.setMinimumSize(QSize(0, 30))
|
self.kofiButton.setMinimumSize(QSize(0, 30))
|
||||||
icon2 = QIcon()
|
icon3 = QIcon()
|
||||||
icon2.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon3.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.kofiButton.setIcon(icon2)
|
self.kofiButton.setIcon(icon3)
|
||||||
self.kofiButton.setIconSize(QSize(19, 16))
|
self.kofiButton.setIconSize(QSize(19, 16))
|
||||||
|
|
||||||
self.horizontalLayout.addWidget(self.kofiButton)
|
self.horizontalLayout.addWidget(self.kofiButton)
|
||||||
@@ -290,9 +317,9 @@ class Ui_mainWindow(object):
|
|||||||
self.wikiButton = QPushButton(self.toolWidget)
|
self.wikiButton = QPushButton(self.toolWidget)
|
||||||
self.wikiButton.setObjectName(u"wikiButton")
|
self.wikiButton.setObjectName(u"wikiButton")
|
||||||
self.wikiButton.setMinimumSize(QSize(0, 30))
|
self.wikiButton.setMinimumSize(QSize(0, 30))
|
||||||
icon3 = QIcon()
|
icon4 = QIcon()
|
||||||
icon3.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon4.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.wikiButton.setIcon(icon3)
|
self.wikiButton.setIcon(icon4)
|
||||||
|
|
||||||
self.horizontalLayout.addWidget(self.wikiButton)
|
self.horizontalLayout.addWidget(self.wikiButton)
|
||||||
|
|
||||||
@@ -336,11 +363,8 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.preserveMarginBox = QSpinBox(self.croppingWidget)
|
self.preserveMarginBox = QSpinBox(self.croppingWidget)
|
||||||
self.preserveMarginBox.setObjectName(u"preserveMarginBox")
|
self.preserveMarginBox.setObjectName(u"preserveMarginBox")
|
||||||
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
sizePolicy1.setHeightForWidth(self.preserveMarginBox.sizePolicy().hasHeightForWidth())
|
||||||
sizePolicy2.setHorizontalStretch(0)
|
self.preserveMarginBox.setSizePolicy(sizePolicy1)
|
||||||
sizePolicy2.setVerticalStretch(0)
|
|
||||||
sizePolicy2.setHeightForWidth(self.preserveMarginBox.sizePolicy().hasHeightForWidth())
|
|
||||||
self.preserveMarginBox.setSizePolicy(sizePolicy2)
|
|
||||||
self.preserveMarginBox.setMaximum(99)
|
self.preserveMarginBox.setMaximum(99)
|
||||||
self.preserveMarginBox.setSingleStep(5)
|
self.preserveMarginBox.setSingleStep(5)
|
||||||
self.preserveMarginBox.setValue(0)
|
self.preserveMarginBox.setValue(0)
|
||||||
@@ -364,18 +388,18 @@ class Ui_mainWindow(object):
|
|||||||
self.convertButton.setObjectName(u"convertButton")
|
self.convertButton.setObjectName(u"convertButton")
|
||||||
self.convertButton.setMinimumSize(QSize(0, 30))
|
self.convertButton.setMinimumSize(QSize(0, 30))
|
||||||
self.convertButton.setFont(font)
|
self.convertButton.setFont(font)
|
||||||
icon4 = QIcon()
|
icon5 = QIcon()
|
||||||
icon4.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon5.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.convertButton.setIcon(icon4)
|
self.convertButton.setIcon(icon5)
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1)
|
self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1)
|
||||||
|
|
||||||
self.clearButton = QPushButton(self.buttonWidget)
|
self.clearButton = QPushButton(self.buttonWidget)
|
||||||
self.clearButton.setObjectName(u"clearButton")
|
self.clearButton.setObjectName(u"clearButton")
|
||||||
self.clearButton.setMinimumSize(QSize(0, 30))
|
self.clearButton.setMinimumSize(QSize(0, 30))
|
||||||
icon5 = QIcon()
|
icon6 = QIcon()
|
||||||
icon5.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon6.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.clearButton.setIcon(icon5)
|
self.clearButton.setIcon(icon6)
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1)
|
self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1)
|
||||||
|
|
||||||
@@ -388,42 +412,35 @@ class Ui_mainWindow(object):
|
|||||||
self.fileButton = QPushButton(self.buttonWidget)
|
self.fileButton = QPushButton(self.buttonWidget)
|
||||||
self.fileButton.setObjectName(u"fileButton")
|
self.fileButton.setObjectName(u"fileButton")
|
||||||
self.fileButton.setMinimumSize(QSize(0, 30))
|
self.fileButton.setMinimumSize(QSize(0, 30))
|
||||||
icon6 = QIcon()
|
icon7 = QIcon()
|
||||||
icon6.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
icon7.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
||||||
self.fileButton.setIcon(icon6)
|
self.fileButton.setIcon(icon7)
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1)
|
self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1)
|
||||||
|
|
||||||
self.defaultOutputFolderButton = QPushButton(self.buttonWidget)
|
self.directoryButton = QPushButton(self.buttonWidget)
|
||||||
self.defaultOutputFolderButton.setObjectName(u"defaultOutputFolderButton")
|
self.directoryButton.setObjectName(u"directoryButton")
|
||||||
self.defaultOutputFolderButton.setMinimumSize(QSize(0, 30))
|
sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
|
||||||
icon7 = QIcon()
|
sizePolicy4.setHorizontalStretch(0)
|
||||||
icon7.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
sizePolicy4.setVerticalStretch(0)
|
||||||
self.defaultOutputFolderButton.setIcon(icon7)
|
sizePolicy4.setHeightForWidth(self.directoryButton.sizePolicy().hasHeightForWidth())
|
||||||
|
self.directoryButton.setSizePolicy(sizePolicy4)
|
||||||
|
self.directoryButton.setIcon(icon1)
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.defaultOutputFolderButton, 0, 5, 1, 1)
|
self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1)
|
||||||
|
|
||||||
self.defaultOutputFolderBox = QCheckBox(self.buttonWidget)
|
|
||||||
self.defaultOutputFolderBox.setObjectName(u"defaultOutputFolderBox")
|
|
||||||
sizePolicy2.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth())
|
|
||||||
self.defaultOutputFolderBox.setSizePolicy(sizePolicy2)
|
|
||||||
self.defaultOutputFolderBox.setTristate(True)
|
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.defaultOutputFolderBox, 0, 4, 1, 1)
|
|
||||||
|
|
||||||
self.formatBox = QComboBox(self.buttonWidget)
|
self.formatBox = QComboBox(self.buttonWidget)
|
||||||
self.formatBox.setObjectName(u"formatBox")
|
self.formatBox.setObjectName(u"formatBox")
|
||||||
self.formatBox.setMinimumSize(QSize(0, 28))
|
self.formatBox.setMinimumSize(QSize(0, 28))
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 2)
|
self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 1)
|
||||||
|
|
||||||
self.clearButton.raise_()
|
self.clearButton.raise_()
|
||||||
self.deviceBox.raise_()
|
self.deviceBox.raise_()
|
||||||
self.convertButton.raise_()
|
self.convertButton.raise_()
|
||||||
self.formatBox.raise_()
|
|
||||||
self.defaultOutputFolderButton.raise_()
|
|
||||||
self.fileButton.raise_()
|
self.fileButton.raise_()
|
||||||
self.defaultOutputFolderBox.raise_()
|
self.directoryButton.raise_()
|
||||||
|
self.formatBox.raise_()
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
|
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
|
||||||
|
|
||||||
@@ -438,11 +455,11 @@ class Ui_mainWindow(object):
|
|||||||
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
|
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
|
||||||
self.chunkSizeLabel = QLabel(self.chunkSizeWidget)
|
self.chunkSizeLabel = QLabel(self.chunkSizeWidget)
|
||||||
self.chunkSizeLabel.setObjectName(u"chunkSizeLabel")
|
self.chunkSizeLabel.setObjectName(u"chunkSizeLabel")
|
||||||
sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
|
sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
|
||||||
sizePolicy4.setHorizontalStretch(0)
|
sizePolicy5.setHorizontalStretch(0)
|
||||||
sizePolicy4.setVerticalStretch(0)
|
sizePolicy5.setVerticalStretch(0)
|
||||||
sizePolicy4.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth())
|
sizePolicy5.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth())
|
||||||
self.chunkSizeLabel.setSizePolicy(sizePolicy4)
|
self.chunkSizeLabel.setSizePolicy(sizePolicy5)
|
||||||
|
|
||||||
self.horizontalLayout_4.addWidget(self.chunkSizeLabel)
|
self.horizontalLayout_4.addWidget(self.chunkSizeLabel)
|
||||||
|
|
||||||
@@ -456,8 +473,8 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget)
|
self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget)
|
||||||
self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel")
|
self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel")
|
||||||
sizePolicy4.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth())
|
sizePolicy5.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth())
|
||||||
self.chunkSizeWarnLabel.setSizePolicy(sizePolicy4)
|
self.chunkSizeWarnLabel.setSizePolicy(sizePolicy5)
|
||||||
|
|
||||||
self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel)
|
self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel)
|
||||||
|
|
||||||
@@ -469,35 +486,43 @@ class Ui_mainWindow(object):
|
|||||||
self.statusBar.setObjectName(u"statusBar")
|
self.statusBar.setObjectName(u"statusBar")
|
||||||
self.statusBar.setSizeGripEnabled(False)
|
self.statusBar.setSizeGripEnabled(False)
|
||||||
mainWindow.setStatusBar(self.statusBar)
|
mainWindow.setStatusBar(self.statusBar)
|
||||||
QWidget.setTabOrder(self.convertButton, self.clearButton)
|
QWidget.setTabOrder(self.jobList, self.fileButton)
|
||||||
|
QWidget.setTabOrder(self.fileButton, self.clearButton)
|
||||||
QWidget.setTabOrder(self.clearButton, self.deviceBox)
|
QWidget.setTabOrder(self.clearButton, self.deviceBox)
|
||||||
QWidget.setTabOrder(self.deviceBox, self.formatBox)
|
QWidget.setTabOrder(self.deviceBox, self.widthBox)
|
||||||
QWidget.setTabOrder(self.formatBox, self.mangaBox)
|
QWidget.setTabOrder(self.widthBox, self.heightBox)
|
||||||
|
QWidget.setTabOrder(self.heightBox, self.convertButton)
|
||||||
|
QWidget.setTabOrder(self.convertButton, self.mangaBox)
|
||||||
QWidget.setTabOrder(self.mangaBox, self.rotateBox)
|
QWidget.setTabOrder(self.mangaBox, self.rotateBox)
|
||||||
QWidget.setTabOrder(self.rotateBox, self.qualityBox)
|
QWidget.setTabOrder(self.rotateBox, self.qualityBox)
|
||||||
QWidget.setTabOrder(self.qualityBox, self.webtoonBox)
|
QWidget.setTabOrder(self.qualityBox, self.webtoonBox)
|
||||||
QWidget.setTabOrder(self.webtoonBox, self.upscaleBox)
|
QWidget.setTabOrder(self.webtoonBox, self.upscaleBox)
|
||||||
QWidget.setTabOrder(self.upscaleBox, self.gammaBox)
|
QWidget.setTabOrder(self.upscaleBox, self.gammaBox)
|
||||||
QWidget.setTabOrder(self.gammaBox, self.borderBox)
|
QWidget.setTabOrder(self.gammaBox, self.gammaSlider)
|
||||||
|
QWidget.setTabOrder(self.gammaSlider, self.borderBox)
|
||||||
QWidget.setTabOrder(self.borderBox, self.outputSplit)
|
QWidget.setTabOrder(self.borderBox, self.outputSplit)
|
||||||
QWidget.setTabOrder(self.outputSplit, self.colorBox)
|
QWidget.setTabOrder(self.outputSplit, self.colorBox)
|
||||||
QWidget.setTabOrder(self.colorBox, self.mozJpegBox)
|
QWidget.setTabOrder(self.colorBox, self.mozJpegBox)
|
||||||
QWidget.setTabOrder(self.mozJpegBox, self.maximizeStrips)
|
QWidget.setTabOrder(self.mozJpegBox, self.maximizeStrips)
|
||||||
QWidget.setTabOrder(self.maximizeStrips, self.croppingBox)
|
QWidget.setTabOrder(self.maximizeStrips, self.croppingBox)
|
||||||
QWidget.setTabOrder(self.croppingBox, self.spreadShiftBox)
|
QWidget.setTabOrder(self.croppingBox, self.croppingPowerSlider)
|
||||||
|
QWidget.setTabOrder(self.croppingPowerSlider, self.preserveMarginBox)
|
||||||
|
QWidget.setTabOrder(self.preserveMarginBox, self.spreadShiftBox)
|
||||||
QWidget.setTabOrder(self.spreadShiftBox, self.deleteBox)
|
QWidget.setTabOrder(self.spreadShiftBox, self.deleteBox)
|
||||||
QWidget.setTabOrder(self.deleteBox, self.disableProcessingBox)
|
QWidget.setTabOrder(self.deleteBox, self.disableProcessingBox)
|
||||||
QWidget.setTabOrder(self.disableProcessingBox, self.chunkSizeBox)
|
QWidget.setTabOrder(self.disableProcessingBox, self.fileFusionBox)
|
||||||
QWidget.setTabOrder(self.chunkSizeBox, self.noRotateBox)
|
QWidget.setTabOrder(self.fileFusionBox, self.noRotateBox)
|
||||||
QWidget.setTabOrder(self.noRotateBox, self.interPanelCropBox)
|
QWidget.setTabOrder(self.noRotateBox, self.interPanelCropBox)
|
||||||
QWidget.setTabOrder(self.interPanelCropBox, self.eraseRainbowBox)
|
QWidget.setTabOrder(self.interPanelCropBox, self.metadataTitleBox)
|
||||||
QWidget.setTabOrder(self.eraseRainbowBox, self.heightBox)
|
QWidget.setTabOrder(self.metadataTitleBox, self.chunkSizeCheckBox)
|
||||||
QWidget.setTabOrder(self.heightBox, self.croppingPowerSlider)
|
QWidget.setTabOrder(self.chunkSizeCheckBox, self.chunkSizeBox)
|
||||||
QWidget.setTabOrder(self.croppingPowerSlider, self.editorButton)
|
QWidget.setTabOrder(self.chunkSizeBox, self.eraseRainbowBox)
|
||||||
QWidget.setTabOrder(self.editorButton, self.wikiButton)
|
QWidget.setTabOrder(self.eraseRainbowBox, self.rotateFirstBox)
|
||||||
QWidget.setTabOrder(self.wikiButton, self.jobList)
|
QWidget.setTabOrder(self.rotateFirstBox, self.autoLevelBox)
|
||||||
QWidget.setTabOrder(self.jobList, self.gammaSlider)
|
QWidget.setTabOrder(self.autoLevelBox, self.autocontrastBox)
|
||||||
QWidget.setTabOrder(self.gammaSlider, self.widthBox)
|
QWidget.setTabOrder(self.autocontrastBox, self.editorButton)
|
||||||
|
QWidget.setTabOrder(self.editorButton, self.kofiButton)
|
||||||
|
QWidget.setTabOrder(self.kofiButton, self.wikiButton)
|
||||||
|
|
||||||
self.retranslateUi(mainWindow)
|
self.retranslateUi(mainWindow)
|
||||||
|
|
||||||
@@ -513,7 +538,7 @@ class Ui_mainWindow(object):
|
|||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>", None))
|
self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left mode", None))
|
self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left (manga)", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>", None))
|
self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
@@ -610,6 +635,14 @@ class Ui_mainWindow(object):
|
|||||||
self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - BW only<br/></span>Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Disabled<br/></span>Disable autocontrast</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - BW and Color<br/></span>BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.</p></body></html>", None))
|
self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - BW only<br/></span>Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Disabled<br/></span>Disable autocontrast</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - BW and Color<br/></span>BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None))
|
self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.defaultOutputFolderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - next to source<br/></span>Place output files next to source files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - folder next to source<br/></span>Place output files in a folder next to source files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Custom<br/></span>Place output files in custom directory specified by right button</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.defaultOutputFolderBox.setText(QCoreApplication.translate("mainWindow", u"Output Folder", None))
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.defaultOutputFolderButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Use this to select the default output directory.</p></body></html>", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
self.defaultOutputFolderButton.setText("")
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html>", None))
|
self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
@@ -650,15 +683,11 @@ class Ui_mainWindow(object):
|
|||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None))
|
self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add file(s)", None))
|
self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add input file(s)", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.defaultOutputFolderButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Use this to select the default output directory.</p></body></html>", None))
|
self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.defaultOutputFolderButton.setText("")
|
self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add input folder(s)", None))
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.defaultOutputFolderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - next to source<br/></span>Place output files next to source files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - folder next to source<br/></span>Place output files in a folder next to source files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Custom<br/></span>Place output files in custom directory specified by right button</p></body></html>", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.defaultOutputFolderBox.setText(QCoreApplication.translate("mainWindow", u"Output Folder", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None))
|
self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
|
|||||||
@@ -156,6 +156,15 @@ class Ui_editorDialog(object):
|
|||||||
|
|
||||||
self.verticalLayout.addWidget(self.optionWidget)
|
self.verticalLayout.addWidget(self.optionWidget)
|
||||||
|
|
||||||
|
QWidget.setTabOrder(self.seriesLine, self.volumeLine)
|
||||||
|
QWidget.setTabOrder(self.volumeLine, self.titleLine)
|
||||||
|
QWidget.setTabOrder(self.titleLine, self.numberLine)
|
||||||
|
QWidget.setTabOrder(self.numberLine, self.writerLine)
|
||||||
|
QWidget.setTabOrder(self.writerLine, self.pencillerLine)
|
||||||
|
QWidget.setTabOrder(self.pencillerLine, self.inkerLine)
|
||||||
|
QWidget.setTabOrder(self.inkerLine, self.coloristLine)
|
||||||
|
QWidget.setTabOrder(self.coloristLine, self.okButton)
|
||||||
|
QWidget.setTabOrder(self.okButton, self.cancelButton)
|
||||||
|
|
||||||
self.retranslateUi(editorDialog)
|
self.retranslateUi(editorDialog)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '9.2.0'
|
__version__ = '9.3.8'
|
||||||
__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'
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ from psutil import virtual_memory, disk_usage
|
|||||||
from html import escape as hescape
|
from html import escape as hescape
|
||||||
import pymupdf
|
import pymupdf
|
||||||
|
|
||||||
from .shared import getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean
|
from .shared import IMAGE_TYPES, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean
|
||||||
from .comicarchive import SEVENZIP, available_archive_tools
|
from .comicarchive import SEVENZIP, available_archive_tools
|
||||||
from . import comic2panel
|
from . import comic2panel
|
||||||
from . import image
|
from . import image
|
||||||
@@ -71,12 +71,23 @@ def main(argv=None):
|
|||||||
if len(sources) == 0:
|
if len(sources) == 0:
|
||||||
print('No matching files found.')
|
print('No matching files found.')
|
||||||
return 1
|
return 1
|
||||||
|
if options.filefusion:
|
||||||
|
fusion_path = makeFusion(list(sources))
|
||||||
|
sources.clear()
|
||||||
|
sources.add(fusion_path)
|
||||||
for source in sources:
|
for source in sources:
|
||||||
source = source.rstrip('\\').rstrip('/')
|
source = source.rstrip('\\').rstrip('/')
|
||||||
options = copy(args)
|
options = copy(args)
|
||||||
options = checkOptions(options)
|
options = checkOptions(options)
|
||||||
print('Working on ' + source + '...')
|
print('Working on ' + source + '...')
|
||||||
makeBook(source)
|
makeBook(source)
|
||||||
|
|
||||||
|
if options.filefusion:
|
||||||
|
for path in sources:
|
||||||
|
if os.path.isfile(path):
|
||||||
|
os.remove(path)
|
||||||
|
elif os.path.isdir(path):
|
||||||
|
rmtree(path, True)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -445,7 +456,7 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
|
|||||||
"</container>"])
|
"</container>"])
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, originalpath, len_tomes=0):
|
def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, originalpath, job_progress='', len_tomes=0):
|
||||||
filelist = []
|
filelist = []
|
||||||
chapterlist = []
|
chapterlist = []
|
||||||
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
||||||
@@ -553,7 +564,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, ori
|
|||||||
else:
|
else:
|
||||||
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
|
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
|
||||||
build_html_end = perf_counter()
|
build_html_end = perf_counter()
|
||||||
print(f"buildHTML: {build_html_end - build_html_start} seconds")
|
print(f"{job_progress}buildHTML: {build_html_end - build_html_start} seconds")
|
||||||
# Overwrite chapternames if ComicInfo.xml has bookmarks
|
# Overwrite chapternames if ComicInfo.xml has bookmarks
|
||||||
if ischunked:
|
if ischunked:
|
||||||
options.comicinfo_chapters = []
|
options.comicinfo_chapters = []
|
||||||
@@ -589,7 +600,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, ori
|
|||||||
buildOPF(path, options.title, filelist, originalpath, cover)
|
buildOPF(path, options.title, filelist, originalpath, cover)
|
||||||
|
|
||||||
|
|
||||||
def buildPDF(path, title, cover=None, output_file=None):
|
def buildPDF(path, title, job_progress='', cover=None, output_file=None):
|
||||||
"""
|
"""
|
||||||
Build a PDF file from processed comic images.
|
Build a PDF file from processed comic images.
|
||||||
Images are combined into a single PDF optimized for e-readers.
|
Images are combined into a single PDF optimized for e-readers.
|
||||||
@@ -614,11 +625,11 @@ def buildPDF(path, title, cover=None, output_file=None):
|
|||||||
# Save with optimizations for smaller file size
|
# Save with optimizations for smaller file size
|
||||||
doc.save(output_file, deflate=True, garbage=4, clean=True)
|
doc.save(output_file, deflate=True, garbage=4, clean=True)
|
||||||
end = perf_counter()
|
end = perf_counter()
|
||||||
print(f"MuPDF output: {end-start} sec")
|
print(f"{job_progress}MuPDF output: {end-start} sec")
|
||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def imgDirectoryProcessing(path):
|
def imgDirectoryProcessing(path, job_progress=''):
|
||||||
global workerPool, workerOutput
|
global workerPool, workerOutput
|
||||||
workerPool = Pool(maxtasksperchild=100)
|
workerPool = Pool(maxtasksperchild=100)
|
||||||
workerOutput = []
|
workerOutput = []
|
||||||
@@ -638,7 +649,7 @@ def imgDirectoryProcessing(path):
|
|||||||
workerPool.close()
|
workerPool.close()
|
||||||
workerPool.join()
|
workerPool.join()
|
||||||
img_processing_end = perf_counter()
|
img_processing_end = perf_counter()
|
||||||
print(f"imgFileProcessing: {img_processing_end - img_processing_start} seconds")
|
print(f"{job_progress}imgFileProcessing: {img_processing_end - img_processing_start} seconds")
|
||||||
|
|
||||||
# macOS 15 likes to add ._ files after multiprocessing
|
# macOS 15 likes to add ._ files after multiprocessing
|
||||||
dot_clean(path)
|
dot_clean(path)
|
||||||
@@ -840,14 +851,16 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getWorkFolder(afile):
|
def getWorkFolder(afile, workdir=None):
|
||||||
|
if not workdir:
|
||||||
|
workdir = mkdtemp('', 'KCC-')
|
||||||
|
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
|
||||||
|
else:
|
||||||
|
fullPath = workdir
|
||||||
if os.path.isdir(afile):
|
if os.path.isdir(afile):
|
||||||
if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
|
if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
|
||||||
raise UserWarning("Not enough disk space to perform conversion.")
|
raise UserWarning("Not enough disk space to perform conversion.")
|
||||||
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
|
||||||
try:
|
try:
|
||||||
os.rmdir(workdir)
|
|
||||||
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
|
|
||||||
copytree(afile, fullPath)
|
copytree(afile, fullPath)
|
||||||
sanitizePermissions(fullPath)
|
sanitizePermissions(fullPath)
|
||||||
return workdir
|
return workdir
|
||||||
@@ -858,7 +871,8 @@ def getWorkFolder(afile):
|
|||||||
if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5:
|
if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5:
|
||||||
raise UserWarning("Not enough disk space to perform conversion.")
|
raise UserWarning("Not enough disk space to perform conversion.")
|
||||||
if afile.lower().endswith('.pdf'):
|
if afile.lower().endswith('.pdf'):
|
||||||
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
if not os.path.exists(fullPath):
|
||||||
|
os.makedirs(fullPath)
|
||||||
path = workdir
|
path = workdir
|
||||||
sanitizePermissions(path)
|
sanitizePermissions(path)
|
||||||
target_height = options.profileData[1][1]
|
target_height = options.profileData[1][1]
|
||||||
@@ -867,39 +881,42 @@ def getWorkFolder(afile):
|
|||||||
elif options.cropping == 2:
|
elif options.cropping == 2:
|
||||||
target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number
|
target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number
|
||||||
try:
|
try:
|
||||||
mupdf_pdf_process_pages_parallel(afile, workdir, target_height)
|
mupdf_pdf_process_pages_parallel(afile, fullPath, target_height)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
rmtree(path, True)
|
rmtree(path, True)
|
||||||
raise UserWarning(f"Failed to extract images from PDF file. {e}")
|
raise UserWarning(f"Failed to extract images from PDF file. {e}")
|
||||||
|
return workdir
|
||||||
else:
|
else:
|
||||||
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
if not os.path.exists(fullPath):
|
||||||
|
os.makedirs(fullPath)
|
||||||
try:
|
try:
|
||||||
cbx = comicarchive.ComicArchive(afile)
|
cbx = comicarchive.ComicArchive(afile)
|
||||||
path = cbx.extract(workdir)
|
path = cbx.extract(fullPath)
|
||||||
sanitizePermissions(path)
|
sanitizePermissions(path)
|
||||||
|
|
||||||
tdir = os.listdir(workdir)
|
tdir = os.listdir(fullPath)
|
||||||
if len(tdir) == 2 and 'ComicInfo.xml' in tdir:
|
if len(tdir) == 2 and 'ComicInfo.xml' in tdir:
|
||||||
tdir.remove('ComicInfo.xml')
|
tdir.remove('ComicInfo.xml')
|
||||||
if os.path.isdir(os.path.join(workdir, tdir[0])):
|
if os.path.isdir(os.path.join(fullPath, tdir[0])):
|
||||||
os.replace(
|
os.replace(
|
||||||
os.path.join(workdir, 'ComicInfo.xml'),
|
os.path.join(fullPath, 'ComicInfo.xml'),
|
||||||
os.path.join(workdir, tdir[0], 'ComicInfo.xml')
|
os.path.join(fullPath, tdir[0], 'ComicInfo.xml')
|
||||||
)
|
)
|
||||||
if len(tdir) == 1 and os.path.isdir(os.path.join(workdir, tdir[0])):
|
if len(tdir) == 1 and os.path.isdir(os.path.join(fullPath, tdir[0])):
|
||||||
path = os.path.join(workdir, tdir[0])
|
for file in os.listdir(os.path.join(fullPath, tdir[0])):
|
||||||
|
move(os.path.join(fullPath, tdir[0], file), fullPath)
|
||||||
|
os.rmdir(os.path.join(fullPath, tdir[0]))
|
||||||
|
return workdir
|
||||||
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
rmtree(workdir, True)
|
rmtree(workdir, True)
|
||||||
raise UserWarning(e)
|
raise UserWarning(e)
|
||||||
else:
|
else:
|
||||||
raise UserWarning("Failed to open source file/directory.")
|
raise UserWarning("Failed to open source file/directory.")
|
||||||
newpath = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
|
||||||
os.renames(path, os.path.join(newpath, 'OEBPS', 'Images'))
|
|
||||||
return newpath
|
|
||||||
|
|
||||||
|
|
||||||
def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
||||||
|
source_path = Path(srcpath)
|
||||||
if srcpath[-1] == os.path.sep:
|
if srcpath[-1] == os.path.sep:
|
||||||
srcpath = srcpath[:-1]
|
srcpath = srcpath[:-1]
|
||||||
if 'Ko' in options.profile and options.format == 'EPUB':
|
if 'Ko' in options.profile and options.format == 'EPUB':
|
||||||
@@ -909,20 +926,29 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
|
|||||||
else:
|
else:
|
||||||
ext = '.kepub.epub'
|
ext = '.kepub.epub'
|
||||||
if wantedname is not None:
|
if wantedname is not None:
|
||||||
|
wanted_root, wanted_ext = os.path.splitext(wantedname)
|
||||||
if wantedname.endswith(ext):
|
if wantedname.endswith(ext):
|
||||||
filename = os.path.abspath(wantedname)
|
filename = os.path.abspath(wantedname)
|
||||||
elif os.path.isdir(srcpath):
|
elif wanted_ext == '.mobi' and ext == '.epub':
|
||||||
filename = os.path.join(os.path.abspath(options.output), os.path.basename(srcpath) + ext)
|
filename = os.path.abspath(wanted_root + ext)
|
||||||
|
# output directory
|
||||||
else:
|
else:
|
||||||
filename = os.path.join(os.path.abspath(options.output),
|
abs_path = os.path.abspath(options.output)
|
||||||
os.path.basename(os.path.splitext(srcpath)[0]) + ext)
|
if not os.path.exists(abs_path):
|
||||||
|
os.mkdir(abs_path)
|
||||||
|
if source_path.is_file():
|
||||||
|
filename = os.path.join(os.path.abspath(options.output), source_path.stem + tomenumber + ext)
|
||||||
|
else:
|
||||||
|
filename = os.path.join(os.path.abspath(options.output), source_path.name + tomenumber + ext)
|
||||||
elif os.path.isdir(srcpath):
|
elif os.path.isdir(srcpath):
|
||||||
filename = srcpath + tomenumber + ext
|
filename = srcpath + tomenumber + ext
|
||||||
else:
|
else:
|
||||||
if 'Ko' in options.profile and options.format == 'EPUB':
|
if 'Ko' in options.profile and options.format == 'EPUB':
|
||||||
src = pathlib.Path(srcpath)
|
if source_path.is_file():
|
||||||
name = re.sub(r'\W+', '_', src.stem) + tomenumber + ext
|
name = re.sub(r'\W+', '_', source_path.stem) + tomenumber + ext
|
||||||
filename = src.with_name(name)
|
else:
|
||||||
|
name = re.sub(r'\W+', '_', source_path.name) + tomenumber + ext
|
||||||
|
filename = source_path.with_name(name)
|
||||||
else:
|
else:
|
||||||
filename = os.path.splitext(srcpath)[0] + tomenumber + ext
|
filename = os.path.splitext(srcpath)[0] + tomenumber + ext
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
@@ -1028,7 +1054,7 @@ def removeNonImages(filetree):
|
|||||||
for root, dirs, files in os.walk(filetree):
|
for root, dirs, files in os.walk(filetree):
|
||||||
for name in files:
|
for name in files:
|
||||||
_, ext = getImageFileName(name)
|
_, ext = getImageFileName(name)
|
||||||
if ext not in ('.png', '.jpg', '.jpeg', '.gif', '.webp', '.jp2', '.avif'):
|
if ext not in IMAGE_TYPES:
|
||||||
if os.path.exists(os.path.join(root, name)):
|
if os.path.exists(os.path.join(root, name)):
|
||||||
os.remove(os.path.join(root, name))
|
os.remove(os.path.join(root, name))
|
||||||
# remove empty nested folders
|
# remove empty nested folders
|
||||||
@@ -1238,7 +1264,7 @@ def slugify(value, is_natural_sorted):
|
|||||||
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def makeZIP(zipfilename, basedir, isepub=False):
|
def makeZIP(zipfilename, basedir, job_progress='', isepub=False):
|
||||||
start = perf_counter()
|
start = perf_counter()
|
||||||
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
zipfilename = os.path.abspath(zipfilename) + '.zip'
|
||||||
if SEVENZIP in available_archive_tools():
|
if SEVENZIP in available_archive_tools():
|
||||||
@@ -1259,7 +1285,7 @@ def makeZIP(zipfilename, basedir, isepub=False):
|
|||||||
zipOutput.write(path, aPath)
|
zipOutput.write(path, aPath)
|
||||||
zipOutput.close()
|
zipOutput.close()
|
||||||
end = perf_counter()
|
end = perf_counter()
|
||||||
print(f"makeZIP time: {end - start} seconds")
|
print(f"{job_progress}makeZIP time: {end - start} seconds")
|
||||||
return zipfilename
|
return zipfilename
|
||||||
|
|
||||||
def makeParser():
|
def makeParser():
|
||||||
@@ -1330,6 +1356,8 @@ def makeParser():
|
|||||||
help="Disable autocontrast.")
|
help="Disable autocontrast.")
|
||||||
output_options.add_argument("--colorautocontrast", action="store_true", dest="colorautocontrast", default=False,
|
output_options.add_argument("--colorautocontrast", action="store_true", dest="colorautocontrast", default=False,
|
||||||
help="Autocontrast color pages too. Skipped for pages without near blacks or whites.")
|
help="Autocontrast color pages too. Skipped for pages without near blacks or whites.")
|
||||||
|
output_options.add_argument("--filefusion", action="store_true", dest="filefusion", default=False,
|
||||||
|
help="Combines all input files into a single file.")
|
||||||
processing_options.add_argument("-c", "--cropping", type=int, dest="cropping", default="2",
|
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]")
|
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",
|
processing_options.add_argument("--cp", "--croppingpower", type=float, dest="croppingp", default="1.0",
|
||||||
@@ -1502,17 +1530,15 @@ def makeFusion(sources: List[str]):
|
|||||||
print(f"Processing {source}...")
|
print(f"Processing {source}...")
|
||||||
checkPre(source)
|
checkPre(source)
|
||||||
print("Checking images...")
|
print("Checking images...")
|
||||||
path = getWorkFolder(source)
|
|
||||||
pathfinder = os.path.join(path, "OEBPS", "Images")
|
|
||||||
sanitizeTree(pathfinder)
|
|
||||||
# TODO: remove flattenTree when subchapters are supported
|
|
||||||
flattenTree(pathfinder)
|
|
||||||
source_path = Path(source)
|
source_path = Path(source)
|
||||||
if source_path.is_file():
|
if source_path.is_file():
|
||||||
os.renames(pathfinder, fusion_path.joinpath(source_path.stem))
|
targetpath = fusion_path.joinpath(source_path.stem)
|
||||||
else:
|
else:
|
||||||
os.renames(pathfinder, fusion_path.joinpath(source_path.name))
|
targetpath = fusion_path.joinpath(source_path.name)
|
||||||
|
getWorkFolder(source, str(targetpath))
|
||||||
|
sanitizeTree(targetpath)
|
||||||
|
# TODO: remove flattenTree when subchapters are supported
|
||||||
|
flattenTree(targetpath)
|
||||||
|
|
||||||
end = perf_counter()
|
end = perf_counter()
|
||||||
print(f"makefusion: {end - start} seconds")
|
print(f"makefusion: {end - start} seconds")
|
||||||
@@ -1521,7 +1547,7 @@ def makeFusion(sources: List[str]):
|
|||||||
return str(fusion_path)
|
return str(fusion_path)
|
||||||
|
|
||||||
|
|
||||||
def makeBook(source, qtgui=None):
|
def makeBook(source, qtgui=None, job_progress=''):
|
||||||
start = perf_counter()
|
start = perf_counter()
|
||||||
global GUI
|
global GUI
|
||||||
GUI = qtgui
|
GUI = qtgui
|
||||||
@@ -1529,11 +1555,12 @@ def makeBook(source, qtgui=None):
|
|||||||
GUI.progressBarTick.emit('1')
|
GUI.progressBarTick.emit('1')
|
||||||
else:
|
else:
|
||||||
checkTools(source)
|
checkTools(source)
|
||||||
|
options.kindle_azw3 = options.iskindle and ('MOBI' in options.format or 'EPUB' in options.format)
|
||||||
options.kindle_scribe_azw3 = options.profile == 'KS' and ('MOBI' in options.format or 'EPUB' in options.format)
|
options.kindle_scribe_azw3 = options.profile == 'KS' and ('MOBI' in options.format or 'EPUB' in options.format)
|
||||||
checkPre(source)
|
checkPre(source)
|
||||||
print("Preparing source images...")
|
print(f"{job_progress}Preparing source images...")
|
||||||
path = getWorkFolder(source)
|
path = getWorkFolder(source)
|
||||||
print("Checking images...")
|
print(f"{job_progress}Checking images...")
|
||||||
getMetadata(os.path.join(path, "OEBPS", "Images"), source)
|
getMetadata(os.path.join(path, "OEBPS", "Images"), source)
|
||||||
removeNonImages(os.path.join(path, "OEBPS", "Images"))
|
removeNonImages(os.path.join(path, "OEBPS", "Images"))
|
||||||
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
|
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
|
||||||
@@ -1544,14 +1571,14 @@ def makeBook(source, qtgui=None):
|
|||||||
|
|
||||||
if options.webtoon:
|
if options.webtoon:
|
||||||
x, y = image.ProfileData.Profiles[options.profile][1]
|
x, y = image.ProfileData.Profiles[options.profile][1]
|
||||||
comic2panel.main(['-y ' + str(y), '-x' + str(x), '-i', '-m', path], qtgui)
|
comic2panel.main(['-y ' + str(y), '-x' + str(x), '-i', '-m', path], job_progress, qtgui)
|
||||||
if options.noprocessing:
|
if options.noprocessing:
|
||||||
print("Do not process image, ignore any profile or processing option")
|
print(f"{job_progress}Do not process image, ignore any profile or processing option")
|
||||||
else:
|
else:
|
||||||
print("Processing images...")
|
print(f"{job_progress}Processing images...")
|
||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit('Processing images')
|
GUI.progressBarTick.emit(f'{job_progress}Processing images')
|
||||||
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"))
|
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"), job_progress)
|
||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit('1')
|
GUI.progressBarTick.emit('1')
|
||||||
if options.batchsplit > 0 or options.targetsize:
|
if options.batchsplit > 0 or options.targetsize:
|
||||||
@@ -1562,11 +1589,11 @@ def makeBook(source, qtgui=None):
|
|||||||
tomeNumber = 0
|
tomeNumber = 0
|
||||||
if GUI:
|
if GUI:
|
||||||
if options.format == 'CBZ':
|
if options.format == 'CBZ':
|
||||||
GUI.progressBarTick.emit('Compressing CBZ files')
|
GUI.progressBarTick.emit(f'{job_progress}Compressing CBZ files')
|
||||||
elif options.format == 'PDF':
|
elif options.format == 'PDF':
|
||||||
GUI.progressBarTick.emit('Creating PDF files')
|
GUI.progressBarTick.emit(f'{job_progress}Creating PDF files')
|
||||||
else:
|
else:
|
||||||
GUI.progressBarTick.emit('Compressing EPUB files')
|
GUI.progressBarTick.emit(f'{job_progress}Compressing EPUB files')
|
||||||
GUI.progressBarTick.emit(str(len(tomes) + 1))
|
GUI.progressBarTick.emit(str(len(tomes) + 1))
|
||||||
GUI.progressBarTick.emit('tick')
|
GUI.progressBarTick.emit('tick')
|
||||||
options.baseTitle = options.title
|
options.baseTitle = options.title
|
||||||
@@ -1580,29 +1607,29 @@ def makeBook(source, qtgui=None):
|
|||||||
tomeNumber += 1
|
tomeNumber += 1
|
||||||
options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']'
|
options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']'
|
||||||
if options.format == 'CBZ':
|
if options.format == 'CBZ':
|
||||||
print("Creating CBZ file...")
|
print(f"{job_progress}Creating CBZ file...")
|
||||||
if len(tomes) > 1:
|
if len(tomes) > 1:
|
||||||
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
|
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
|
||||||
else:
|
else:
|
||||||
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
|
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
|
||||||
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"))
|
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress)
|
||||||
elif options.format == 'PDF':
|
elif options.format == 'PDF':
|
||||||
print("Creating PDF file with PyMuPDF...")
|
print(f"{job_progress}Creating PDF file with PyMuPDF...")
|
||||||
# determine output filename based on source and tome count
|
# determine output filename based on source and tome count
|
||||||
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
|
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
|
||||||
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
|
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
|
||||||
# use optimized buildPDF logic with streaming and compression
|
# use optimized buildPDF logic with streaming and compression
|
||||||
output_pdf = buildPDF(tome, options.title, None, output_file)
|
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file)
|
||||||
filepath.append(output_pdf)
|
filepath.append(output_pdf)
|
||||||
else:
|
else:
|
||||||
print("Creating EPUB file...")
|
print(f"{job_progress}Creating EPUB file...")
|
||||||
if len(tomes) > 1:
|
if len(tomes) > 1:
|
||||||
buildEPUB(tome, chapterNames, tomeNumber, True, cover, source, len(tomes))
|
buildEPUB(tome, chapterNames, tomeNumber, True, cover, source, job_progress, len(tomes))
|
||||||
filepath.append(getOutputFilename(source, options.output, '.epub', ' ' + str(tomeNumber)))
|
filepath.append(getOutputFilename(source, options.output, '.epub', ' ' + str(tomeNumber)))
|
||||||
else:
|
else:
|
||||||
buildEPUB(tome, chapterNames, tomeNumber, False, cover, source)
|
buildEPUB(tome, chapterNames, tomeNumber, False, cover, source, job_progress)
|
||||||
filepath.append(getOutputFilename(source, options.output, '.epub', ''))
|
filepath.append(getOutputFilename(source, options.output, '.epub', ''))
|
||||||
makeZIP(tome + '_comic', tome, True)
|
makeZIP(tome + '_comic', tome, job_progress, True)
|
||||||
# Copy files to final destination (PDF files are already saved directly)
|
# Copy files to final destination (PDF files are already saved directly)
|
||||||
if options.format != 'PDF':
|
if options.format != 'PDF':
|
||||||
copyfile(tome + '_comic.zip', filepath[-1])
|
copyfile(tome + '_comic.zip', filepath[-1])
|
||||||
@@ -1615,23 +1642,23 @@ def makeBook(source, qtgui=None):
|
|||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit('tick')
|
GUI.progressBarTick.emit('tick')
|
||||||
if not GUI and options.format == 'MOBI':
|
if not GUI and options.format == 'MOBI':
|
||||||
print("Creating MOBI files...")
|
print(f"{job_progress}Creating MOBI files...")
|
||||||
work = []
|
work = []
|
||||||
for i in filepath:
|
for i in filepath:
|
||||||
work.append([i])
|
work.append([i])
|
||||||
output = makeMOBI(work, GUI)
|
output = makeMOBI(work, GUI)
|
||||||
for errors in output:
|
for errors in output:
|
||||||
if errors[0] != 0:
|
if errors[0] != 0:
|
||||||
print('Error: KindleGen failed to create MOBI!')
|
print(f"{job_progress}Error: KindleGen failed to create MOBI!")
|
||||||
print(errors)
|
print(errors)
|
||||||
return filepath
|
return filepath
|
||||||
k = kindle.Kindle(options.profile)
|
k = kindle.Kindle(options.profile)
|
||||||
if k.path and k.coverSupport:
|
if k.path and k.coverSupport:
|
||||||
print("Kindle detected. Uploading covers...")
|
print(f"{job_progress}Kindle detected. Uploading covers...")
|
||||||
for i in filepath:
|
for i in filepath:
|
||||||
output = makeMOBIFix(i, options.covers[filepath.index(i)][1])
|
output = makeMOBIFix(i, options.covers[filepath.index(i)][1])
|
||||||
if not output[0]:
|
if not output[0]:
|
||||||
print('Error: Failed to tweak KindleGen output!')
|
print(f'{job_progress}Error: Failed to tweak KindleGen output!')
|
||||||
return filepath
|
return filepath
|
||||||
else:
|
else:
|
||||||
os.remove(i.replace('.epub', '.mobi') + '_toclean')
|
os.remove(i.replace('.epub', '.mobi') + '_toclean')
|
||||||
@@ -1644,7 +1671,7 @@ def makeBook(source, qtgui=None):
|
|||||||
rmtree(source, True)
|
rmtree(source, True)
|
||||||
|
|
||||||
end = perf_counter()
|
end = perf_counter()
|
||||||
print(f"makeBook: {end - start} seconds")
|
print(f"{job_progress}makeBook: {end - start} seconds")
|
||||||
# Clean up temporary workspace
|
# Clean up temporary workspace
|
||||||
try:
|
try:
|
||||||
rmtree(path, True)
|
rmtree(path, True)
|
||||||
|
|||||||
@@ -24,10 +24,12 @@ import sys
|
|||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
from PIL import Image, ImageChops, ImageOps, ImageDraw, ImageFilter
|
from PIL import Image, ImageChops, ImageOps, ImageDraw, ImageFilter, ImageFile
|
||||||
from PIL.Image import Dither
|
from PIL.Image import Dither
|
||||||
from .shared import dot_clean, getImageFileName, walkLevel, walkSort, sanitizeTrace
|
from .shared import dot_clean, getImageFileName, walkLevel, walkSort, sanitizeTrace
|
||||||
|
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
||||||
def mergeDirectoryTick(output):
|
def mergeDirectoryTick(output):
|
||||||
if output:
|
if output:
|
||||||
@@ -60,18 +62,19 @@ def mergeDirectory(work):
|
|||||||
imagesValid.append(i[0])
|
imagesValid.append(i[0])
|
||||||
# Silently drop directories that contain too many images
|
# Silently drop directories that contain too many images
|
||||||
# 131072 = GIMP_MAX_IMAGE_SIZE / 4
|
# 131072 = GIMP_MAX_IMAGE_SIZE / 4
|
||||||
if targetHeight > 131072 * 2:
|
if targetHeight > 131072 * 3:
|
||||||
raise RuntimeError(f'Image too tall at {targetHeight} pixels.')
|
raise RuntimeError(f'Image too tall at {targetHeight} pixels. {targetWidth} pixels wide. Try using separate chapter folders or file fusion.')
|
||||||
result = Image.new('RGB', (targetWidth, targetHeight))
|
result = Image.new('RGB', (targetWidth, targetHeight))
|
||||||
y = 0
|
y = 0
|
||||||
for i in imagesValid:
|
for i in imagesValid:
|
||||||
img = Image.open(i).convert('RGB')
|
with Image.open(i) as img:
|
||||||
if img.size[0] < targetWidth or img.size[0] > targetWidth:
|
img = img.convert('RGB')
|
||||||
widthPercent = (targetWidth / float(img.size[0]))
|
if img.size[0] < targetWidth or img.size[0] > targetWidth:
|
||||||
heightSize = int((float(img.size[1]) * float(widthPercent)))
|
widthPercent = (targetWidth / float(img.size[0]))
|
||||||
img = ImageOps.fit(img, (targetWidth, heightSize), method=Image.BICUBIC, centering=(0.5, 0.5))
|
heightSize = int((float(img.size[1]) * float(widthPercent)))
|
||||||
result.paste(img, (0, y))
|
img = ImageOps.fit(img, (targetWidth, heightSize), method=Image.BICUBIC, centering=(0.5, 0.5))
|
||||||
y += img.size[1]
|
result.paste(img, (0, y))
|
||||||
|
y += img.size[1]
|
||||||
os.remove(i)
|
os.remove(i)
|
||||||
savePath = os.path.split(imagesValid[0])
|
savePath = os.path.split(imagesValid[0])
|
||||||
result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG')
|
result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG')
|
||||||
@@ -219,7 +222,7 @@ def splitImage(work):
|
|||||||
return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
|
return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None, qtgui=None):
|
def main(argv=None, job_progress='', qtgui=None):
|
||||||
global args, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput
|
global args, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput
|
||||||
parser = ArgumentParser(prog="kcc-c2p", usage="kcc-c2p [options] [input]", add_help=False)
|
parser = ArgumentParser(prog="kcc-c2p", usage="kcc-c2p [options] [input]", add_help=False)
|
||||||
|
|
||||||
@@ -251,16 +254,14 @@ def main(argv=None, qtgui=None):
|
|||||||
return 1
|
return 1
|
||||||
if args.height > 0:
|
if args.height > 0:
|
||||||
for sourceDir in args.input:
|
for sourceDir in args.input:
|
||||||
targetDir = sourceDir + "-Splitted"
|
targetDir = sourceDir
|
||||||
if os.path.isdir(sourceDir):
|
if os.path.isdir(sourceDir):
|
||||||
rmtree(targetDir, True)
|
|
||||||
os.renames(sourceDir, targetDir)
|
|
||||||
work = []
|
work = []
|
||||||
pagenumber = 1
|
pagenumber = 1
|
||||||
splitWorkerOutput = []
|
splitWorkerOutput = []
|
||||||
splitWorkerPool = Pool(maxtasksperchild=10)
|
splitWorkerPool = Pool(maxtasksperchild=10)
|
||||||
if args.merge:
|
if args.merge:
|
||||||
print("Merging images...")
|
print(f"{job_progress}Merging images...")
|
||||||
directoryNumer = 1
|
directoryNumer = 1
|
||||||
mergeWork = []
|
mergeWork = []
|
||||||
mergeWorkerOutput = []
|
mergeWorkerOutput = []
|
||||||
@@ -272,7 +273,7 @@ def main(argv=None, qtgui=None):
|
|||||||
directoryNumer += 1
|
directoryNumer += 1
|
||||||
mergeWork.append([os.path.join(root, directory)])
|
mergeWork.append([os.path.join(root, directory)])
|
||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit('Combining images')
|
GUI.progressBarTick.emit(f'{job_progress}Combining images')
|
||||||
GUI.progressBarTick.emit(str(directoryNumer))
|
GUI.progressBarTick.emit(str(directoryNumer))
|
||||||
for i in mergeWork:
|
for i in mergeWork:
|
||||||
mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick)
|
mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick)
|
||||||
@@ -285,7 +286,7 @@ def main(argv=None, qtgui=None):
|
|||||||
rmtree(targetDir, True)
|
rmtree(targetDir, True)
|
||||||
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
|
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
|
||||||
mergeWorkerOutput[0][1])
|
mergeWorkerOutput[0][1])
|
||||||
print("Splitting images...")
|
print(f"{job_progress}Splitting images...")
|
||||||
dot_clean(targetDir)
|
dot_clean(targetDir)
|
||||||
for root, _, files in os.walk(targetDir, False):
|
for root, _, files in os.walk(targetDir, False):
|
||||||
for name in files:
|
for name in files:
|
||||||
@@ -295,7 +296,7 @@ def main(argv=None, qtgui=None):
|
|||||||
else:
|
else:
|
||||||
os.remove(os.path.join(root, name))
|
os.remove(os.path.join(root, name))
|
||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit('Splitting images')
|
GUI.progressBarTick.emit(f'{job_progress}Splitting images')
|
||||||
GUI.progressBarTick.emit(str(pagenumber))
|
GUI.progressBarTick.emit(str(pagenumber))
|
||||||
GUI.progressBarTick.emit('tick')
|
GUI.progressBarTick.emit('tick')
|
||||||
if len(work) > 0:
|
if len(work) > 0:
|
||||||
@@ -311,8 +312,6 @@ def main(argv=None, qtgui=None):
|
|||||||
rmtree(targetDir, True)
|
rmtree(targetDir, True)
|
||||||
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
|
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
|
||||||
splitWorkerOutput[0][1])
|
splitWorkerOutput[0][1])
|
||||||
if args.inPlace:
|
|
||||||
os.renames(targetDir, sourceDir)
|
|
||||||
else:
|
else:
|
||||||
rmtree(targetDir, True)
|
rmtree(targetDir, True)
|
||||||
raise UserWarning("C2P: Source directory is empty.")
|
raise UserWarning("C2P: Source directory is empty.")
|
||||||
|
|||||||
@@ -20,12 +20,13 @@
|
|||||||
|
|
||||||
from functools import cached_property, lru_cache
|
from functools import cached_property, lru_cache
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import platform
|
import platform
|
||||||
import distro
|
import distro
|
||||||
from subprocess import STDOUT, PIPE, CalledProcessError
|
from subprocess import STDOUT, PIPE, CalledProcessError
|
||||||
from xml.dom.minidom import parseString
|
from xml.dom.minidom import parseString
|
||||||
from xml.parsers.expat import ExpatError
|
from xml.parsers.expat import ExpatError
|
||||||
from .shared import subprocess_run
|
from .shared import IMAGE_TYPES, subprocess_run
|
||||||
|
|
||||||
EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.'
|
EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.'
|
||||||
SEVENZIP = '7zz' if platform.system() == 'Darwin' else '7z'
|
SEVENZIP = '7zz' if platform.system() == 'Darwin' else '7z'
|
||||||
@@ -65,6 +66,9 @@ class ComicArchive:
|
|||||||
def extract(self, targetdir):
|
def extract(self, targetdir):
|
||||||
if not os.path.isdir(targetdir):
|
if not os.path.isdir(targetdir):
|
||||||
raise OSError('Target directory doesn\'t exist.')
|
raise OSError('Target directory doesn\'t exist.')
|
||||||
|
|
||||||
|
if Path(self.basename).suffix.lower() in IMAGE_TYPES:
|
||||||
|
raise UserWarning('Put images into folder and drag and drop folder into KCC window.')
|
||||||
|
|
||||||
missing = []
|
missing = []
|
||||||
|
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ import numpy as np
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
import mozjpeg_lossless_optimization
|
import mozjpeg_lossless_optimization
|
||||||
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter, ImageDraw
|
from PIL import Image, ImageOps, ImageFile, ImageChops, ImageDraw
|
||||||
|
|
||||||
from .rainbow_artifacts_eraser import erase_rainbow_artifacts
|
from .rainbow_artifacts_eraser import erase_rainbow_artifacts
|
||||||
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
|
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
|
||||||
from .inter_panel_crop_alg import crop_empty_inter_panel
|
from .inter_panel_crop_alg import crop_empty_inter_panel
|
||||||
|
|
||||||
AUTO_CROP_THRESHOLD = 0.015
|
AUTO_CROP_THRESHOLD = 0.015
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
||||||
class ProfileData:
|
class ProfileData:
|
||||||
@@ -85,6 +86,9 @@ class ProfileData:
|
|||||||
]
|
]
|
||||||
|
|
||||||
ProfilesKindleEBOK = {
|
ProfilesKindleEBOK = {
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilesKindlePDOC = {
|
||||||
'K1': ("Kindle 1", (600, 670), Palette4, 1.0),
|
'K1': ("Kindle 1", (600, 670), Palette4, 1.0),
|
||||||
'K2': ("Kindle 2", (600, 670), Palette15, 1.0),
|
'K2': ("Kindle 2", (600, 670), Palette15, 1.0),
|
||||||
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.0),
|
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.0),
|
||||||
@@ -92,9 +96,6 @@ class ProfileData:
|
|||||||
'K57': ("Kindle 5/7", (600, 800), Palette16, 1.0),
|
'K57': ("Kindle 5/7", (600, 800), Palette16, 1.0),
|
||||||
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.0),
|
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.0),
|
||||||
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0),
|
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0),
|
||||||
}
|
|
||||||
|
|
||||||
ProfilesKindlePDOC = {
|
|
||||||
'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.0),
|
'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.0),
|
||||||
'K810': ("Kindle 8/10", (600, 800), Palette16, 1.0),
|
'K810': ("Kindle 8/10", (600, 800), Palette16, 1.0),
|
||||||
'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0),
|
'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0),
|
||||||
@@ -262,7 +263,6 @@ class ComicPage:
|
|||||||
_, self.size, self.palette, self.gamma = self.opt.profileData
|
_, self.size, self.palette, self.gamma = self.opt.profileData
|
||||||
if self.opt.hq:
|
if self.opt.hq:
|
||||||
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
|
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
|
||||||
self.kindle_scribe_azw3 = (options.profile == 'KS') and (options.format in ('MOBI', 'EPUB'))
|
|
||||||
self.original_color_mode = image.mode
|
self.original_color_mode = image.mode
|
||||||
# TODO: color check earlier
|
# TODO: color check earlier
|
||||||
self.image = image.convert("RGB")
|
self.image = image.convert("RGB")
|
||||||
@@ -296,9 +296,9 @@ class ComicPage:
|
|||||||
|
|
||||||
# cut off pixels from both ends of the histogram to remove jpg compression artifacts
|
# cut off pixels from both ends of the histogram to remove jpg compression artifacts
|
||||||
# for better accuracy, you could split the image in half and analyze each half separately
|
# for better accuracy, you could split the image in half and analyze each half separately
|
||||||
def histograms_cutoff(self, cb, cr, cutoff=(2, 2)):
|
def histograms_cutoff(self, cb_hist, cr_hist, cutoff=(2, 2)):
|
||||||
cb_hist = cb.histogram()
|
if cutoff == (0, 0):
|
||||||
cr_hist = cr.histogram()
|
return cb_hist, cr_hist
|
||||||
|
|
||||||
for h in cb_hist, cr_hist:
|
for h in cb_hist, cr_hist:
|
||||||
# get number of pixels
|
# get number of pixels
|
||||||
@@ -327,60 +327,50 @@ class ComicPage:
|
|||||||
break
|
break
|
||||||
return cb_hist, cr_hist
|
return cb_hist, cr_hist
|
||||||
|
|
||||||
|
def color_precision(self, cb_hist_original, cr_hist_original, cutoff, diff_threshold):
|
||||||
|
cb_hist, cr_hist = self.histograms_cutoff(cb_hist_original.copy(), cr_hist_original.copy(), cutoff)
|
||||||
|
|
||||||
|
cb_nonzero = [i for i, e in enumerate(cb_hist) if e]
|
||||||
|
cr_nonzero = [i for i, e in enumerate(cr_hist) if e]
|
||||||
|
cb_spread = cb_nonzero[-1] - cb_nonzero[0]
|
||||||
|
cr_spread = cr_nonzero[-1] - cr_nonzero[0]
|
||||||
|
|
||||||
|
# bias adjustment, don't go lower than 7
|
||||||
|
SPREAD_THRESHOLD = 7
|
||||||
|
if self.opt.forcecolor:
|
||||||
|
if any([
|
||||||
|
cb_nonzero[0] > 128,
|
||||||
|
cr_nonzero[0] > 128,
|
||||||
|
cb_nonzero[-1] < 128,
|
||||||
|
cr_nonzero[-1] < 128,
|
||||||
|
]):
|
||||||
|
return True, True
|
||||||
|
elif cb_spread < SPREAD_THRESHOLD and cr_spread < SPREAD_THRESHOLD:
|
||||||
|
return True, False
|
||||||
|
|
||||||
|
DIFF_THRESHOLD = diff_threshold
|
||||||
|
if any([
|
||||||
|
cb_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
||||||
|
cr_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
||||||
|
cb_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
||||||
|
cr_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
||||||
|
]):
|
||||||
|
return True, True
|
||||||
|
|
||||||
|
return False, None
|
||||||
|
|
||||||
def calculate_color(self):
|
def calculate_color(self):
|
||||||
img = self.image.convert("YCbCr")
|
img = self.image.convert("YCbCr")
|
||||||
_, cb, cr = img.split()
|
_, cb, cr = img.split()
|
||||||
|
cb_hist_original = cb.histogram()
|
||||||
|
cr_hist_original = cr.histogram()
|
||||||
|
|
||||||
# get rid of some jpg compression
|
# you can increase 22 but don't increase 10. 4 maybe can go higher
|
||||||
cutoff = (.2, .2)
|
for cutoff, diff_threshold in [((0, 0), 22), ((.2, .2), 10), ((3, 3), 4)]:
|
||||||
cb_hist, cr_hist = self.histograms_cutoff(cb, cr, cutoff)
|
done, decision = self.color_precision(cb_hist_original, cr_hist_original, cutoff, diff_threshold)
|
||||||
|
if done:
|
||||||
cb_nonzero = [i for i, e in enumerate(cb_hist) if e]
|
return decision
|
||||||
cr_nonzero = [i for i, e in enumerate(cr_hist) if e]
|
return False
|
||||||
cb_spread = cb_nonzero[-1] - cb_nonzero[0]
|
|
||||||
cr_spread = cr_nonzero[-1] - cr_nonzero[0]
|
|
||||||
|
|
||||||
# bias adjustment
|
|
||||||
SPREAD_THRESHOLD = 5
|
|
||||||
if not self.opt.forcecolor and cb_spread < SPREAD_THRESHOLD and cr_spread < SPREAD_THRESHOLD:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# check for large amount of extreme colors
|
|
||||||
# 11 if too high. 10 is barely enough. If needed make it magnitude of both
|
|
||||||
DIFF_THRESHOLD = 10
|
|
||||||
if any([
|
|
||||||
cb_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
|
||||||
cr_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
|
||||||
cb_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
|
||||||
cr_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
|
||||||
]):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# get ride of most jpg compression
|
|
||||||
cutoff = (2, 2)
|
|
||||||
cb_hist, cr_hist = self.histograms_cutoff(cb, cr, cutoff)
|
|
||||||
|
|
||||||
cb_nonzero = [i for i, e in enumerate(cb_hist) if e]
|
|
||||||
cr_nonzero = [i for i, e in enumerate(cr_hist) if e]
|
|
||||||
cb_spread = cb_nonzero[-1] - cb_nonzero[0]
|
|
||||||
cr_spread = cr_nonzero[-1] - cr_nonzero[0]
|
|
||||||
|
|
||||||
# bias adjustment
|
|
||||||
SPREAD_THRESHOLD = 5
|
|
||||||
if not self.opt.forcecolor and cb_spread < SPREAD_THRESHOLD and cr_spread < SPREAD_THRESHOLD:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# check for any amount of mild colors still remaining
|
|
||||||
DIFF_THRESHOLD = 6
|
|
||||||
if any([
|
|
||||||
cb_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
|
||||||
cr_nonzero[0] <= 128 - DIFF_THRESHOLD,
|
|
||||||
cb_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
|
||||||
cr_nonzero[-1] >= 128 + DIFF_THRESHOLD,
|
|
||||||
]):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def saveToDir(self):
|
def saveToDir(self):
|
||||||
try:
|
try:
|
||||||
@@ -405,7 +395,7 @@ class ComicPage:
|
|||||||
|
|
||||||
def save_with_codec(self, image, targetPath):
|
def save_with_codec(self, image, targetPath):
|
||||||
if self.opt.forcepng:
|
if self.opt.forcepng:
|
||||||
image.info["transparency"] = None
|
image.info.pop('transparency', None)
|
||||||
if self.opt.iskindle and ('MOBI' in self.opt.format or 'EPUB' in self.opt.format):
|
if self.opt.iskindle and ('MOBI' in self.opt.format or 'EPUB' in self.opt.format):
|
||||||
targetPath += '.gif'
|
targetPath += '.gif'
|
||||||
image.save(targetPath, 'GIF', optimize=1, interlace=False)
|
image.save(targetPath, 'GIF', optimize=1, interlace=False)
|
||||||
@@ -490,6 +480,14 @@ class ComicPage:
|
|||||||
self.image = erase_rainbow_artifacts(self.image, is_color)
|
self.image = erase_rainbow_artifacts(self.image, is_color)
|
||||||
|
|
||||||
def resizeImage(self):
|
def resizeImage(self):
|
||||||
|
if self.opt.norotate and self.targetPathOrder in ('-kcc-a', '-kcc-d') and not self.opt.kindle_scribe_azw3:
|
||||||
|
# TODO: Kindle Scribe case
|
||||||
|
if self.opt.kindle_azw3 and any(dim > 1920 for dim in self.image.size):
|
||||||
|
self.image = ImageOps.contain(self.image, (1920, 1920), Image.Resampling.LANCZOS)
|
||||||
|
elif self.image.size[0] > self.size[0] * 2 or self.image.size[1] > self.size[1]:
|
||||||
|
self.image = ImageOps.contain(self.image, (self.size[0] * 2, self.size[1]), Image.Resampling.LANCZOS)
|
||||||
|
return
|
||||||
|
|
||||||
ratio_device = float(self.size[1]) / float(self.size[0])
|
ratio_device = float(self.size[1]) / float(self.size[0])
|
||||||
ratio_image = float(self.image.size[1]) / float(self.image.size[0])
|
ratio_image = float(self.image.size[1]) / float(self.image.size[0])
|
||||||
method = self.resize_method()
|
method = self.resize_method()
|
||||||
@@ -557,7 +555,7 @@ class Cover:
|
|||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
self.image = self.image.convert('RGB')
|
self.image = self.image.convert('RGB')
|
||||||
self.image = ImageOps.autocontrast(self.image)
|
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
|
||||||
if not self.options.forcecolor:
|
if not self.options.forcecolor:
|
||||||
self.image = self.image.convert('L')
|
self.image = self.image.convert('L')
|
||||||
self.crop_main_cover()
|
self.crop_main_cover()
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from PIL import Image, ImageFilter, ImageOps
|
from PIL import Image, ImageFilter, ImageOps, ImageFile
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
from .common_crop import threshold_from_power, group_close_values
|
from .common_crop import threshold_from_power, group_close_values
|
||||||
|
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Crops inter-panel empty spaces (ignores empty spaces near borders - for that use crop margins).
|
Crops inter-panel empty spaces (ignores empty spaces near borders - for that use crop margins).
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
from PIL import ImageOps, ImageFilter
|
from PIL import ImageOps, ImageFilter, ImageFile
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from .common_crop import threshold_from_power, group_close_values
|
from .common_crop import threshold_from_power, group_close_values
|
||||||
|
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Some assupmptions on the page number sizes
|
Some assupmptions on the page number sizes
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image, ImageFile
|
||||||
|
|
||||||
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
|
|
||||||
|
|
||||||
def fourier_transform_image(img):
|
def fourier_transform_image(img):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ import sys
|
|||||||
from traceback import format_tb
|
from traceback import format_tb
|
||||||
|
|
||||||
|
|
||||||
|
IMAGE_TYPES = ('.png', '.jpg', '.jpeg', '.gif', '.webp', '.jp2', '.avif')
|
||||||
|
|
||||||
|
|
||||||
class HTMLStripper(HTMLParser):
|
class HTMLStripper(HTMLParser):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
HTMLParser.__init__(self)
|
HTMLParser.__init__(self)
|
||||||
|
|||||||
11
requirements-docker.txt
Normal file
11
requirements-docker.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Pillow>=11.3.0
|
||||||
|
psutil>=5.9.5
|
||||||
|
requests>=2.31.0
|
||||||
|
python-slugify>=1.2.1
|
||||||
|
packaging>=23.2
|
||||||
|
mozjpeg-lossless-optimization>=1.2.0
|
||||||
|
natsort>=8.4.0
|
||||||
|
distro>=1.8.0
|
||||||
|
# Below requirements are compiled in Dockefile
|
||||||
|
# numpy==2.3.4
|
||||||
|
# PyMuPDF==1.26.6
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
PySide6==6.5.2
|
PySide6==6.4.3
|
||||||
Pillow>=11.3.0
|
Pillow>=11.3.0
|
||||||
psutil>=5.9.5
|
psutil>=5.9.5
|
||||||
requests>=2.31.0
|
requests>=2.31.0
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ PySide6<6.10
|
|||||||
Pillow>=11.3.0
|
Pillow>=11.3.0
|
||||||
psutil>=5.9.5
|
psutil>=5.9.5
|
||||||
requests>=2.31.0
|
requests>=2.31.0
|
||||||
python-slugify>=1.2.1
|
python-slugify>=1.2.1,<9.0.0
|
||||||
raven>=6.0.0
|
raven>=6.0.0
|
||||||
packaging>=23.2
|
packaging>=23.2
|
||||||
mozjpeg-lossless-optimization>=1.2.0
|
mozjpeg-lossless-optimization>=1.2.0
|
||||||
|
|||||||
80
setup.py
80
setup.py
@@ -8,6 +8,8 @@ Install as Python package:
|
|||||||
|
|
||||||
Create EXE/APP:
|
Create EXE/APP:
|
||||||
python3 setup.py build_binary
|
python3 setup.py build_binary
|
||||||
|
python3 setup.py build_c2e
|
||||||
|
python3 setup.py build_c2p
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -38,8 +40,8 @@ class BuildBinaryCommand(setuptools.Command):
|
|||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
|
||||||
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
|
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
|
||||||
min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET')
|
min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET', '')
|
||||||
if min_os:
|
if min_os.startswith('10.1'):
|
||||||
os.system(f'appdmg kcc.json dist/kcc_osx_{min_os.replace(".", "_")}_legacy_{VERSION}.dmg')
|
os.system(f'appdmg kcc.json dist/kcc_osx_{min_os.replace(".", "_")}_legacy_{VERSION}.dmg')
|
||||||
else:
|
else:
|
||||||
os.system(f'appdmg kcc.json dist/kcc_macos_{platform.processor()}_{VERSION}.dmg')
|
os.system(f'appdmg kcc.json dist/kcc_macos_{platform.processor()}_{VERSION}.dmg')
|
||||||
@@ -57,10 +59,75 @@ class BuildBinaryCommand(setuptools.Command):
|
|||||||
else:
|
else:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
class BuildC2ECommand(setuptools.Command):
|
||||||
|
description = 'build binary c2e release'
|
||||||
|
user_options = []
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
def run(self):
|
||||||
|
VERSION = __version__
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "KCC C2E" -c -s kcc-c2e.py')
|
||||||
|
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
if os.getenv('WINDOWS_7'):
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n kcc_c2e_win7_legacy_' + VERSION + ' -c --noupx kcc-c2e.py')
|
||||||
|
else:
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n kcc_c2e_' + VERSION + ' -c --noupx kcc-c2e.py')
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.platform == 'linux':
|
||||||
|
os.system(
|
||||||
|
'pyinstaller --hidden-import=_cffi_backend --hidden-import=queue -y -F -i icons/comic2ebook.ico -n kcc_c2e_linux_' + VERSION + ' kcc-c2e.py')
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
class BuildC2PCommand(setuptools.Command):
|
||||||
|
description = 'build binary c2p release'
|
||||||
|
user_options = []
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
def run(self):
|
||||||
|
VERSION = __version__
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -n "KCC C2P" -c -s kcc-c2p.py')
|
||||||
|
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
if os.getenv('WINDOWS_7'):
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n kcc_c2p_win7_legacy_' + VERSION + ' -c --noupx kcc-c2p.py')
|
||||||
|
else:
|
||||||
|
os.system('pyinstaller --hidden-import=_cffi_backend -y -F -i icons\\comic2ebook.ico -n kcc_c2p_' + VERSION + ' -c --noupx kcc-c2p.py')
|
||||||
|
sys.exit(0)
|
||||||
|
elif sys.platform == 'linux':
|
||||||
|
os.system(
|
||||||
|
'pyinstaller --hidden-import=_cffi_backend --hidden-import=queue -y -F -i icons/comic2ebook.ico -n kcc_c2p_linux_' + VERSION + ' kcc-c2p.py')
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
cmdclass={
|
cmdclass={
|
||||||
'build_binary': BuildBinaryCommand,
|
'build_binary': BuildBinaryCommand,
|
||||||
|
'build_c2e': BuildC2ECommand,
|
||||||
|
'build_c2p': BuildC2PCommand,
|
||||||
},
|
},
|
||||||
name=NAME,
|
name=NAME,
|
||||||
version=VERSION,
|
version=VERSION,
|
||||||
@@ -81,16 +148,15 @@ setuptools.setup(
|
|||||||
},
|
},
|
||||||
packages=['kindlecomicconverter'],
|
packages=['kindlecomicconverter'],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'pyside6>=6.0.0',
|
'PySide6>=6.0.0',
|
||||||
'Pillow>=9.3.0',
|
'Pillow>=9.3.0',
|
||||||
'PyMuPDF>=1.18.0',
|
|
||||||
'psutil>=5.9.5',
|
'psutil>=5.9.5',
|
||||||
|
'requests>=2.31.0',
|
||||||
'python-slugify>=1.2.1,<9.0.0',
|
'python-slugify>=1.2.1,<9.0.0',
|
||||||
'raven>=6.0.0',
|
'raven>=6.0.0',
|
||||||
'requests>=2.31.0',
|
'mozjpeg-lossless-optimization>=1.2.0',
|
||||||
'mozjpeg-lossless-optimization>=1.1.2',
|
|
||||||
'natsort>=8.4.0',
|
'natsort>=8.4.0',
|
||||||
'distro',
|
'distro>=1.8.0',
|
||||||
'numpy>=1.22.4',
|
'numpy>=1.22.4',
|
||||||
'PyMuPDF>=1.16.1',
|
'PyMuPDF>=1.16.1',
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user