mirror of
https://github.com/ciromattia/kcc
synced 2026-04-18 06:58:58 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2981c0ceb | ||
|
|
8268552ac7 | ||
|
|
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
|
||||||
|
|||||||
65
.github/workflows/docker-base-publish.yml
vendored
65
.github/workflows/docker-base-publish.yml
vendored
@@ -1,34 +1,45 @@
|
|||||||
name: Docker base
|
name: Build and Publish Base Docker Image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
tags: [ 'docker-base-*' ]
|
branches:
|
||||||
|
- master
|
||||||
# Don't trigger if it's just a documentation update
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
- '**.MD'
|
|
||||||
- '**.yml'
|
|
||||||
- 'docs/**'
|
|
||||||
- 'LICENSE'
|
|
||||||
- '.gitattributes'
|
|
||||||
- '.gitignore'
|
|
||||||
- '.dockerignore'
|
|
||||||
|
|
||||||
|
paths:
|
||||||
|
- 'requirements-docker.txt'
|
||||||
|
- 'Dockerfile-base'
|
||||||
|
|
||||||
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:
|
||||||
docker_build_file: ./Dockerfile-base
|
- name: Login to GitHub Container Registry
|
||||||
platform_linux_arm32v7_enabled: true
|
uses: docker/login-action@v3
|
||||||
platform_linux_arm64v8_enabled: true
|
with:
|
||||||
platform_linux_amd64_enabled: true
|
registry: ghcr.io
|
||||||
push_enabled: true
|
username: ${{ github.repository_owner }}
|
||||||
build_nohealthcheck: false
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
ghcr_repo_owner: ${{ github.repository_owner }}
|
|
||||||
ghcr_repo: ${{ github.repository }}
|
- name: Set up QEMU
|
||||||
build_latest: false
|
uses: docker/setup-qemu-action@v3
|
||||||
secrets:
|
|
||||||
ghcr_token: ${{ secrets.GITHUB_TOKEN }}
|
- 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: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
file: Dockerfile-base
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
ghcr.io/${{ github.repository_owner }}/kcc:base-latest
|
||||||
|
ghcr.io/${{ github.repository_owner }}/kcc:base-${{ steps.release_date.outputs.release_date }}
|
||||||
|
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/kcc:base-cache
|
||||||
|
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/kcc:base-cache,mode=max
|
||||||
|
|||||||
54
.github/workflows/docker-publish.yml
vendored
54
.github/workflows/docker-publish.yml
vendored
@@ -1,12 +1,12 @@
|
|||||||
name: Docker
|
name: Build and publish final 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 or a base image update
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '**.MD'
|
- '**.MD'
|
||||||
@@ -15,19 +15,37 @@ on:
|
|||||||
- 'LICENSE'
|
- 'LICENSE'
|
||||||
- '.gitattributes'
|
- '.gitattributes'
|
||||||
- '.gitignore'
|
- '.gitignore'
|
||||||
- '.dockerignore'
|
- 'requirements-docker.txt'
|
||||||
|
- 'Dockerfile-base'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_push:
|
build_and_publish_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: Login to GitHub Container Registry
|
||||||
platform_linux_arm64v8_enabled: true
|
uses: docker/login-action@v3
|
||||||
platform_linux_amd64_enabled: true
|
with:
|
||||||
push_enabled: true
|
registry: ghcr.io
|
||||||
build_nohealthcheck: false
|
username: ${{ github.repository_owner }}
|
||||||
ghcr_repo_owner: ${{ github.repository_owner }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
ghcr_repo: ${{ github.repository }}
|
|
||||||
secrets:
|
- name: Set up QEMU
|
||||||
ghcr_token: ${{ secrets.GITHUB_TOKEN }}
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository_owner }}/kcc
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/kcc:final-cache
|
||||||
|
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/kcc:final-cache,mode=max
|
||||||
|
|||||||
1
.github/workflows/package-macos.yml
vendored
1
.github/workflows/package-macos.yml
vendored
@@ -89,7 +89,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
|
||||||
|
|||||||
@@ -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
|
|
||||||
15
.github/workflows/package-windows.yml
vendored
15
.github/workflows/package-windows.yml
vendored
@@ -23,6 +23,16 @@ 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@v5
|
||||||
@@ -40,12 +50,12 @@ 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@v4
|
||||||
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@v1.3
|
||||||
@@ -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
|
||||||
|
|||||||
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/
|
||||||
|
|||||||
35
Dockerfile
35
Dockerfile
@@ -1,19 +1,20 @@
|
|||||||
# Select final stage based on TARGETARCH ARG
|
FROM ghcr.io/ciromattia/kcc:base-latest
|
||||||
FROM ghcr.io/ciromattia/kcc:docker-base-20241116
|
|
||||||
LABEL com.kcc.name="Kindle Comic Converter"
|
|
||||||
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
|
COPY . /opt/kcc/
|
||||||
RUN cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
|
|
||||||
|
|
||||||
ENTRYPOINT ["/opt/kcc/kcc-c2e.py"]
|
# Setup executable and version file
|
||||||
CMD ["-h"]
|
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 && \
|
||||||
|
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.description='Kindle Comic Converter' \
|
||||||
|
org.opencontainers.image.source='https://github.com/ciromattia/kcc' \
|
||||||
|
org.opencontainers.image.title="Kindle Comic Converter"
|
||||||
|
|
||||||
|
ENTRYPOINT ["entrypoint"]
|
||||||
|
CMD ["-h"]
|
||||||
188
Dockerfile-base
188
Dockerfile-base
@@ -1,164 +1,44 @@
|
|||||||
FROM --platform=linux/amd64 python:3.13-slim-bullseye as compile-amd64
|
# STAGE 1: BUILDER
|
||||||
ARG TARGETOS
|
# Contains all build tools and dev dependencies, will be discarded
|
||||||
|
FROM python:3.13-slim-bullseye AS builder
|
||||||
|
|
||||||
ARG TARGETARCH
|
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"
|
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
RUN set -x && \
|
RUN set -x && \
|
||||||
TEMP_PACKAGES=() && \
|
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" && \
|
||||||
KEPT_PACKAGES=() && \
|
RUNTIME_DEPS="bash ca-certificates chrpath locales locales-all libfreetype6 libfontconfig1 p7zip-full python3 python3-pip unrar-free libgl1" && \
|
||||||
# Packages only required during build
|
DEBIAN_FRONTEND=noninteractive apt-get update -y && \
|
||||||
TEMP_PACKAGES+=(build-essential) && \
|
apt-get install -y --no-install-recommends ${BUILD_DEPS} ${RUNTIME_DEPS}
|
||||||
TEMP_PACKAGES+=(cmake) && \
|
|
||||||
TEMP_PACKAGES+=(libfreetype6-dev) && \
|
# Install Python dependencies using virtual environment
|
||||||
TEMP_PACKAGES+=(libfontconfig1-dev) && \
|
COPY requirements-docker.txt .
|
||||||
TEMP_PACKAGES+=(libpng-dev) && \
|
RUN \
|
||||||
TEMP_PACKAGES+=(libjpeg-dev) && \
|
set -x && \
|
||||||
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 venv /opt/venv && \
|
||||||
python -m pip install -r /opt/kcc/requirements.txt
|
. /opt/venv/bin/activate && \
|
||||||
|
pip install --upgrade pip && \
|
||||||
|
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 unrar-free && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
FROM --platform=linux/arm/v7 python:3.13-slim-bullseye as compile-armv7
|
# Copy artifacts from builder
|
||||||
ARG TARGETOS
|
COPY --from=builder /opt/venv /opt/venv
|
||||||
ARG TARGETARCH
|
|
||||||
ARG TARGETVARIANT
|
|
||||||
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
|
|
||||||
|
|
||||||
ENV LC_ALL=C.UTF-8 \
|
WORKDIR /opt/kcc
|
||||||
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"
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
|
||||||
RUN set -x && \
|
LABEL com.kcc.name="Kindle Comic Converter" \
|
||||||
TEMP_PACKAGES=() && \
|
com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi" \
|
||||||
KEPT_PACKAGES=() && \
|
org.opencontainers.image.description='Kindle Comic Converter Base Image' \
|
||||||
# Packages only required during build
|
org.opencontainers.image.source='https://github.com/ciromattia/kcc' \
|
||||||
TEMP_PACKAGES+=(build-essential) && \
|
org.opencontainers.image.title="Kindle Comic Converter Base Image"
|
||||||
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
|
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
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
|
||||||
@@ -416,7 +416,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 +442,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>
|
||||||
|
|||||||
@@ -522,7 +522,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:
|
||||||
@@ -876,6 +876,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()
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ class Ui_mainWindow(object):
|
|||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '9.2.0'
|
__version__ = '9.3.0'
|
||||||
__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'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
@@ -1330,6 +1341,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",
|
||||||
|
|||||||
@@ -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,8 +62,8 @@ 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:
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -296,9 +297,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 +328,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 +396,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)
|
||||||
@@ -557,7 +548,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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
10
requirements-docker.txt
Normal file
10
requirements-docker.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
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
|
||||||
|
numpy>=1.22.4
|
||||||
|
PyMuPDF>=1.18.0
|
||||||
67
setup.py
67
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
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user