mirror of
https://github.com/ciromattia/kcc
synced 2026-04-18 15:08:48 +00:00
Compare commits
5 Commits
v5.6.2
...
docker-bas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c98d6179c3 | ||
|
|
37200bdca0 | ||
|
|
0193bcd00a | ||
|
|
0bbe9348a2 | ||
|
|
d16628dc59 |
34
.github/workflows/docker-base-publish.yml
vendored
Normal file
34
.github/workflows/docker-base-publish.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
name: Docker base
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
tags: [ 'docker-base-*' ]
|
||||||
|
|
||||||
|
# Don't trigger if it's just a documentation update
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- '**.MD'
|
||||||
|
- '**.yml'
|
||||||
|
- 'docs/**'
|
||||||
|
- 'LICENSE'
|
||||||
|
- '.gitattributes'
|
||||||
|
- '.gitignore'
|
||||||
|
- '.dockerignore'
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_push:
|
||||||
|
uses: sdr-enthusiasts/common-github-workflows/.github/workflows/build_and_push_image.yml@main
|
||||||
|
with:
|
||||||
|
docker_build_file: ./Dockerfile-base
|
||||||
|
platform_linux_arm32v7_enabled: true
|
||||||
|
platform_linux_arm64v8_enabled: true
|
||||||
|
platform_linux_amd64_enabled: true
|
||||||
|
push_enabled: true
|
||||||
|
build_nohealthcheck: false
|
||||||
|
ghcr_repo_owner: ${{ github.repository_owner }}
|
||||||
|
ghcr_repo: ${{ github.repository }}
|
||||||
|
build_latest: false
|
||||||
|
secrets:
|
||||||
|
ghcr_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
3
.github/workflows/docker-publish.yml
vendored
3
.github/workflows/docker-publish.yml
vendored
@@ -2,10 +2,7 @@ name: Docker
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
#schedule:
|
|
||||||
# - cron: '39 5 * * *'
|
|
||||||
push:
|
push:
|
||||||
# branches: [ master, pipeline_test, docker_test ]
|
|
||||||
# Publish semver tags as releases.
|
# Publish semver tags as releases.
|
||||||
tags: [ 'v*.*.*' ]
|
tags: [ 'v*.*.*' ]
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/package-linux.yml
vendored
2
.github/workflows/package-linux.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Install python dependencies
|
- name: Install python dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libpng-dev libjpeg-dev p7zip-full python3-pyqt5 python3-pip squashfs-tools libfuse2
|
sudo apt-get install -y libpng-dev libjpeg-dev p7zip-full p7zip-rar python3-pyqt5 python3-pip squashfs-tools libfuse2
|
||||||
python -m pip install --upgrade pip setuptools wheel certifi pyinstaller PyQt6 --no-binary pyinstaller
|
python -m pip install --upgrade pip setuptools wheel certifi pyinstaller PyQt6 --no-binary pyinstaller
|
||||||
python -m pip install -r requirements.txt
|
python -m pip install -r requirements.txt
|
||||||
- name: build binary
|
- name: build binary
|
||||||
|
|||||||
152
Dockerfile
152
Dockerfile
@@ -1,147 +1,5 @@
|
|||||||
FROM --platform=linux/amd64 python:3.11-slim-buster as compile-amd64
|
|
||||||
ARG TARGETOS
|
|
||||||
ARG TARGETARCH
|
|
||||||
ARG TARGETVARIANT
|
|
||||||
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
|
|
||||||
|
|
||||||
|
|
||||||
COPY requirements.txt /opt/kcc/
|
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
|
||||||
apt-get install -y libpng-dev libjpeg-dev p7zip-full unrar-free libgl1 python3-pyqt5 && \
|
|
||||||
python -m pip install --upgrade pip && \
|
|
||||||
python -m venv /opt/venv && \
|
|
||||||
python -m pip install -r /opt/kcc/requirements.txt
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################################
|
|
||||||
|
|
||||||
FROM --platform=linux/arm64 python:3.11-slim-buster as compile-arm64
|
|
||||||
ARG TARGETOS
|
|
||||||
ARG TARGETARCH
|
|
||||||
ARG TARGETVARIANT
|
|
||||||
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
|
|
||||||
|
|
||||||
ENV LC_ALL=C.UTF-8 \
|
|
||||||
LANG=C.UTF-8 \
|
|
||||||
LANGUAGE=en_US:en
|
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
|
||||||
|
|
||||||
RUN set -x && \
|
|
||||||
TEMP_PACKAGES=() && \
|
|
||||||
KEPT_PACKAGES=() && \
|
|
||||||
# Packages only required during build
|
|
||||||
TEMP_PACKAGES+=(build-essential) && \
|
|
||||||
TEMP_PACKAGES+=(cmake) && \
|
|
||||||
TEMP_PACKAGES+=(libfreetype6-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libfontconfig1-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libpng-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libjpeg-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libssl-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libxft-dev) && \
|
|
||||||
TEMP_PACKAGES+=(make) && \
|
|
||||||
TEMP_PACKAGES+=(python3-dev) && \
|
|
||||||
TEMP_PACKAGES+=(python3-setuptools) && \
|
|
||||||
TEMP_PACKAGES+=(python3-wheel) && \
|
|
||||||
# Packages kept in the image
|
|
||||||
KEPT_PACKAGES+=(bash) && \
|
|
||||||
KEPT_PACKAGES+=(ca-certificates) && \
|
|
||||||
KEPT_PACKAGES+=(chrpath) && \
|
|
||||||
KEPT_PACKAGES+=(locales) && \
|
|
||||||
KEPT_PACKAGES+=(locales-all) && \
|
|
||||||
KEPT_PACKAGES+=(libfreetype6) && \
|
|
||||||
KEPT_PACKAGES+=(libfontconfig1) && \
|
|
||||||
KEPT_PACKAGES+=(p7zip-full) && \
|
|
||||||
KEPT_PACKAGES+=(python3) && \
|
|
||||||
KEPT_PACKAGES+=(python3-pip) && \
|
|
||||||
KEPT_PACKAGES+=(python-pyqt5) && \
|
|
||||||
KEPT_PACKAGES+=(qt5-default) && \
|
|
||||||
KEPT_PACKAGES+=(unrar-free) && \
|
|
||||||
# Install packages
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
${KEPT_PACKAGES[@]} \
|
|
||||||
${TEMP_PACKAGES[@]} \
|
|
||||||
&& \
|
|
||||||
# Install required python modules
|
|
||||||
python -m pip install --upgrade pip && \
|
|
||||||
# python -m pip install -r /opt/kcc/requirements.txt && \
|
|
||||||
python -m venv /opt/venv && \
|
|
||||||
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################################
|
|
||||||
|
|
||||||
FROM --platform=linux/arm/v7 python:3.11-slim-buster as compile-armv7
|
|
||||||
ARG TARGETOS
|
|
||||||
ARG TARGETARCH
|
|
||||||
ARG TARGETVARIANT
|
|
||||||
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
|
|
||||||
|
|
||||||
ENV LC_ALL=C.UTF-8 \
|
|
||||||
LANG=C.UTF-8 \
|
|
||||||
LANGUAGE=en_US:en
|
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
|
||||||
|
|
||||||
RUN set -x && \
|
|
||||||
TEMP_PACKAGES=() && \
|
|
||||||
KEPT_PACKAGES=() && \
|
|
||||||
# Packages only required during build
|
|
||||||
TEMP_PACKAGES+=(build-essential) && \
|
|
||||||
TEMP_PACKAGES+=(cmake) && \
|
|
||||||
TEMP_PACKAGES+=(libffi-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libfreetype6-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libfontconfig1-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libpng-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libjpeg-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libssl-dev) && \
|
|
||||||
TEMP_PACKAGES+=(libxft-dev) && \
|
|
||||||
TEMP_PACKAGES+=(make) && \
|
|
||||||
TEMP_PACKAGES+=(python3-dev) && \
|
|
||||||
TEMP_PACKAGES+=(python3-setuptools) && \
|
|
||||||
TEMP_PACKAGES+=(python3-wheel) && \
|
|
||||||
# Packages kept in the image
|
|
||||||
KEPT_PACKAGES+=(bash) && \
|
|
||||||
KEPT_PACKAGES+=(ca-certificates) && \
|
|
||||||
KEPT_PACKAGES+=(chrpath) && \
|
|
||||||
KEPT_PACKAGES+=(locales) && \
|
|
||||||
KEPT_PACKAGES+=(locales-all) && \
|
|
||||||
KEPT_PACKAGES+=(libfreetype6) && \
|
|
||||||
KEPT_PACKAGES+=(libfontconfig1) && \
|
|
||||||
KEPT_PACKAGES+=(p7zip-full) && \
|
|
||||||
KEPT_PACKAGES+=(python3) && \
|
|
||||||
KEPT_PACKAGES+=(python3-pip) && \
|
|
||||||
KEPT_PACKAGES+=(python-pyqt5) && \
|
|
||||||
KEPT_PACKAGES+=(qt5-default) && \
|
|
||||||
KEPT_PACKAGES+=(unrar-free) && \
|
|
||||||
# Install packages
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
||||||
${KEPT_PACKAGES[@]} \
|
|
||||||
${TEMP_PACKAGES[@]} \
|
|
||||||
&& \
|
|
||||||
# Install required python modules
|
|
||||||
python -m pip install --upgrade pip && \
|
|
||||||
# python -m pip install -r /opt/kcc/requirements.txt && \
|
|
||||||
python -m venv /opt/venv && \
|
|
||||||
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
|
|
||||||
|
|
||||||
|
|
||||||
######################################################################################
|
|
||||||
FROM --platform=linux/amd64 python:3.11-slim-buster as build-amd64
|
|
||||||
COPY --from=compile-amd64 /opt/venv /opt/venv
|
|
||||||
|
|
||||||
FROM --platform=linux/arm64 python:3.11-slim-buster as build-arm64
|
|
||||||
COPY --from=compile-arm64 /opt/venv /opt/venv
|
|
||||||
|
|
||||||
FROM --platform=linux/arm/v7 python:3.11-slim-buster as build-armv7
|
|
||||||
COPY --from=compile-armv7 /opt/venv /opt/venv
|
|
||||||
######################################################################################
|
|
||||||
|
|
||||||
# Select final stage based on TARGETARCH ARG
|
# Select final stage based on TARGETARCH ARG
|
||||||
FROM build-${TARGETARCH}${TARGETVARIANT}
|
FROM ghcr.io/ciromattia/kcc:docker-base-20230514
|
||||||
LABEL com.kcc.name="Kindle Comic Converter"
|
LABEL com.kcc.name="Kindle Comic Converter"
|
||||||
LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi"
|
LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi"
|
||||||
LABEL org.opencontainers.image.description='Kindle Comic Converter'
|
LABEL org.opencontainers.image.description='Kindle Comic Converter'
|
||||||
@@ -154,14 +12,8 @@ LABEL org.opencontainers.image.vendor='ciromattia'
|
|||||||
LABEL org.opencontainers.image.licenses='ISC'
|
LABEL org.opencontainers.image.licenses='ISC'
|
||||||
LABEL org.opencontainers.image.title="Kindle Comic Converter"
|
LABEL org.opencontainers.image.title="Kindle Comic Converter"
|
||||||
|
|
||||||
|
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . /opt/kcc
|
COPY . /opt/kcc
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
RUN cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
|
||||||
apt-get install -y p7zip-full unrar-free && \
|
|
||||||
ln -s /app/kindlegen /bin/kindlegen && \
|
|
||||||
cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
|
|
||||||
|
|
||||||
ENTRYPOINT ["/opt/kcc/kcc-c2e.py"]
|
ENTRYPOINT ["/opt/kcc/kcc-c2e.py"]
|
||||||
CMD ["-h"]
|
CMD ["-h"]
|
||||||
162
Dockerfile-base
Normal file
162
Dockerfile-base
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
FROM --platform=linux/amd64 python:3.11-slim-bullseye as compile-amd64
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG TARGETVARIANT
|
||||||
|
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
|
||||||
|
|
||||||
|
|
||||||
|
COPY requirements.txt /opt/kcc/
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||||
|
apt-get install -y libpng-dev libjpeg-dev p7zip-full unrar-free libgl1 python3-pyqt5 && \
|
||||||
|
python -m pip install --upgrade pip && \
|
||||||
|
python -m venv /opt/venv && \
|
||||||
|
python -m pip install -r /opt/kcc/requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################################
|
||||||
|
|
||||||
|
FROM --platform=linux/arm64 python:3.11-slim-bullseye as compile-arm64
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG TARGETVARIANT
|
||||||
|
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
|
||||||
|
|
||||||
|
ENV LC_ALL=C.UTF-8 \
|
||||||
|
LANG=C.UTF-8 \
|
||||||
|
LANGUAGE=en_US:en
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN set -x && \
|
||||||
|
TEMP_PACKAGES=() && \
|
||||||
|
KEPT_PACKAGES=() && \
|
||||||
|
# Packages only required during build
|
||||||
|
TEMP_PACKAGES+=(build-essential) && \
|
||||||
|
TEMP_PACKAGES+=(cmake) && \
|
||||||
|
TEMP_PACKAGES+=(libfreetype6-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libfontconfig1-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libpng-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libjpeg-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libssl-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libxft-dev) && \
|
||||||
|
TEMP_PACKAGES+=(make) && \
|
||||||
|
TEMP_PACKAGES+=(python3-dev) && \
|
||||||
|
TEMP_PACKAGES+=(python3-setuptools) && \
|
||||||
|
TEMP_PACKAGES+=(python3-wheel) && \
|
||||||
|
# Packages kept in the image
|
||||||
|
KEPT_PACKAGES+=(bash) && \
|
||||||
|
KEPT_PACKAGES+=(ca-certificates) && \
|
||||||
|
KEPT_PACKAGES+=(chrpath) && \
|
||||||
|
KEPT_PACKAGES+=(locales) && \
|
||||||
|
KEPT_PACKAGES+=(locales-all) && \
|
||||||
|
KEPT_PACKAGES+=(libfreetype6) && \
|
||||||
|
KEPT_PACKAGES+=(libfontconfig1) && \
|
||||||
|
KEPT_PACKAGES+=(p7zip-full) && \
|
||||||
|
KEPT_PACKAGES+=(python3) && \
|
||||||
|
KEPT_PACKAGES+=(python3-pip) && \
|
||||||
|
KEPT_PACKAGES+=(python3-pyqt5) && \
|
||||||
|
KEPT_PACKAGES+=(unrar-free) && \
|
||||||
|
# Install packages
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
|
${KEPT_PACKAGES[@]} \
|
||||||
|
${TEMP_PACKAGES[@]} \
|
||||||
|
&& \
|
||||||
|
# Install required python modules
|
||||||
|
python -m pip install --upgrade pip && \
|
||||||
|
# python -m pip install -r /opt/kcc/requirements.txt && \
|
||||||
|
python -m venv /opt/venv && \
|
||||||
|
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################################
|
||||||
|
|
||||||
|
FROM --platform=linux/arm/v7 python:3.11-slim-bullseye as compile-armv7
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG TARGETVARIANT
|
||||||
|
RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
|
||||||
|
|
||||||
|
ENV LC_ALL=C.UTF-8 \
|
||||||
|
LANG=C.UTF-8 \
|
||||||
|
LANGUAGE=en_US:en
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN set -x && \
|
||||||
|
TEMP_PACKAGES=() && \
|
||||||
|
KEPT_PACKAGES=() && \
|
||||||
|
# Packages only required during build
|
||||||
|
TEMP_PACKAGES+=(build-essential) && \
|
||||||
|
TEMP_PACKAGES+=(cmake) && \
|
||||||
|
TEMP_PACKAGES+=(libffi-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libfreetype6-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libfontconfig1-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libpng-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libjpeg-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libssl-dev) && \
|
||||||
|
TEMP_PACKAGES+=(libxft-dev) && \
|
||||||
|
TEMP_PACKAGES+=(make) && \
|
||||||
|
TEMP_PACKAGES+=(python3-dev) && \
|
||||||
|
TEMP_PACKAGES+=(python3-setuptools) && \
|
||||||
|
TEMP_PACKAGES+=(python3-wheel) && \
|
||||||
|
# Packages kept in the image
|
||||||
|
KEPT_PACKAGES+=(bash) && \
|
||||||
|
KEPT_PACKAGES+=(ca-certificates) && \
|
||||||
|
KEPT_PACKAGES+=(chrpath) && \
|
||||||
|
KEPT_PACKAGES+=(locales) && \
|
||||||
|
KEPT_PACKAGES+=(locales-all) && \
|
||||||
|
KEPT_PACKAGES+=(libfreetype6) && \
|
||||||
|
KEPT_PACKAGES+=(libfontconfig1) && \
|
||||||
|
KEPT_PACKAGES+=(p7zip-full) && \
|
||||||
|
KEPT_PACKAGES+=(python3) && \
|
||||||
|
KEPT_PACKAGES+=(python3-pip) && \
|
||||||
|
KEPT_PACKAGES+=(python3-pyqt5) && \
|
||||||
|
KEPT_PACKAGES+=(unrar-free) && \
|
||||||
|
# Install packages
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||||
|
${KEPT_PACKAGES[@]} \
|
||||||
|
${TEMP_PACKAGES[@]} \
|
||||||
|
&& \
|
||||||
|
# Install required python modules
|
||||||
|
python -m pip install --upgrade pip && \
|
||||||
|
# python -m pip install -r /opt/kcc/requirements.txt && \
|
||||||
|
python -m venv /opt/venv && \
|
||||||
|
python -m pip install --upgrade pillow python-slugify psutil raven mozjpeg-lossless-optimization
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################################
|
||||||
|
FROM --platform=linux/amd64 python:3.11-slim-bullseye as build-amd64
|
||||||
|
COPY --from=compile-amd64 /opt/venv /opt/venv
|
||||||
|
|
||||||
|
FROM --platform=linux/arm64 python:3.11-slim-bullseye as build-arm64
|
||||||
|
COPY --from=compile-arm64 /opt/venv /opt/venv
|
||||||
|
|
||||||
|
FROM --platform=linux/arm/v7 python:3.11-slim-bullseye as build-armv7
|
||||||
|
COPY --from=compile-armv7 /opt/venv /opt/venv
|
||||||
|
######################################################################################
|
||||||
|
|
||||||
|
# Select final stage based on TARGETARCH ARG
|
||||||
|
FROM build-${TARGETARCH}${TARGETVARIANT}
|
||||||
|
LABEL com.kcc.name="Kindle Comic Converter base image"
|
||||||
|
LABEL com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi"
|
||||||
|
LABEL org.opencontainers.image.description='Kindle Comic Converter base image'
|
||||||
|
LABEL org.opencontainers.image.documentation='https://github.com/ciromattia/kcc'
|
||||||
|
LABEL org.opencontainers.image.source='https://github.com/ciromattia/kcc'
|
||||||
|
LABEL org.opencontainers.image.authors='darodi'
|
||||||
|
LABEL org.opencontainers.image.url='https://github.com/ciromattia/kcc'
|
||||||
|
LABEL org.opencontainers.image.documentation='https://github.com/ciromattia/kcc'
|
||||||
|
LABEL org.opencontainers.image.vendor='ciromattia'
|
||||||
|
LABEL org.opencontainers.image.licenses='ISC'
|
||||||
|
LABEL org.opencontainers.image.title="Kindle Comic Converter"
|
||||||
|
|
||||||
|
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
WORKDIR /app
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||||
|
apt-get install -y p7zip-full unrar-free && \
|
||||||
|
ln -s /app/kindlegen /bin/kindlegen && \
|
||||||
|
echo docker-base-20230514 > /IMAGE_VERSION
|
||||||
|
|
||||||
124
README.md
124
README.md
@@ -136,89 +136,81 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi
|
|||||||
### Standalone `kcc-c2e.py` usage:
|
### Standalone `kcc-c2e.py` usage:
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: kcc-c2e [options] comic_file|comic_folder
|
usage: kcc-c2e [options] [input]
|
||||||
|
|
||||||
Options:
|
MANDATORY:
|
||||||
MAIN:
|
input Full path to comic folder or file(s) to be processed.
|
||||||
-p PROFILE, --profile=PROFILE
|
|
||||||
Device profile (Available options: K1, K2, K34, K578,
|
|
||||||
KDX, KPW, KPW5, KV, KO, K11, KS, KoMT, KoG, KoGHD,
|
|
||||||
KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoL, KoF, KoS,
|
|
||||||
KoE) [Default=KV]
|
|
||||||
-m, --manga-style Manga style (right-to-left reading and splitting)
|
|
||||||
-q, --hq Try to increase the quality of magnification
|
|
||||||
-2, --two-panel Display two not four panels in Panel View mode
|
|
||||||
-w, --webtoon Webtoon processing mode
|
|
||||||
--targetsize=TARGETSIZE
|
|
||||||
the maximal size of output file in MB. [Default=100MB
|
|
||||||
for webtoon and 400MB for others]
|
|
||||||
|
|
||||||
OUTPUT SETTINGS:
|
MAIN:
|
||||||
-o OUTPUT, --output=OUTPUT
|
-p PROFILE, --profile PROFILE
|
||||||
Output generated file to specified directory or file
|
Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KPW5, KV, KO, K11, KS, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoL, KoF, KoS, KoE) [Default=KV]
|
||||||
-t TITLE, --title=TITLE
|
-m, --manga-style Manga style (right-to-left reading and splitting)
|
||||||
Comic title [Default=filename or directory name]
|
-q, --hq Try to increase the quality of magnification
|
||||||
-f FORMAT, --format=FORMAT
|
-2, --two-panel Display two not four panels in Panel View mode
|
||||||
Output format (Available options: Auto, MOBI, EPUB,
|
-w, --webtoon Webtoon processing mode
|
||||||
CBZ, KFX, MOBI+EPUB) [Default=Auto]
|
--ts TARGETSIZE, --targetsize TARGETSIZE
|
||||||
-b BATCHSPLIT, --batchsplit=BATCHSPLIT
|
the maximal size of output file in MB. [Default=100MB for webtoon and 400MB for others]
|
||||||
Split output into multiple files. 0: Don't split 1:
|
|
||||||
Automatic mode 2: Consider every subdirectory as
|
|
||||||
separate volume [Default=0]
|
|
||||||
|
|
||||||
PROCESSING:
|
PROCESSING:
|
||||||
-n, --noprocessing Do not modify image and ignore any profil or
|
-n, --noprocessing Do not modify image and ignore any profil or processing option
|
||||||
processing option
|
-u, --upscale Resize images smaller than device's resolution
|
||||||
-u, --upscale Resize images smaller than device's resolution
|
-s, --stretch Stretch images to device's resolution
|
||||||
-s, --stretch Stretch images to device's resolution
|
-r SPLITTER, --splitter SPLITTER
|
||||||
-r SPLITTER, --splitter=SPLITTER
|
Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]
|
||||||
Double page parsing mode. 0: Split 1: Rotate 2: Both
|
-g GAMMA, --gamma GAMMA
|
||||||
[Default=0]
|
Apply gamma correction to linearize the image [Default=Auto]
|
||||||
-g GAMMA, --gamma=GAMMA
|
-c CROPPING, --cropping CROPPING
|
||||||
Apply gamma correction to linearize the image
|
Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]
|
||||||
[Default=Auto]
|
--cp CROPPINGP, --croppingpower CROPPINGP
|
||||||
-c CROPPING, --cropping=CROPPING
|
|
||||||
Set cropping mode. 0: Disabled 1: Margins 2: Margins +
|
|
||||||
page numbers [Default=2]
|
|
||||||
--cp=CROPPINGP, --croppingpower=CROPPINGP
|
|
||||||
Set cropping power [Default=1.0]
|
Set cropping power [Default=1.0]
|
||||||
--cm=CROPPINGM, --croppingminimum=CROPPINGM
|
--cm CROPPINGM, --croppingminimum CROPPINGM
|
||||||
Set cropping minimum area ratio [Default=0.0]
|
Set cropping minimum area ratio [Default=0.0]
|
||||||
--blackborders Disable autodetection and force black borders
|
--blackborders Disable autodetection and force black borders
|
||||||
--whiteborders Disable autodetection and force white borders
|
--whiteborders Disable autodetection and force white borders
|
||||||
--forcecolor Don't convert images to grayscale
|
--forcecolor Don't convert images to grayscale
|
||||||
--forcepng Create PNG files instead JPEG
|
--forcepng Create PNG files instead JPEG
|
||||||
--mozjpeg Create JPEG files using mozJpeg
|
--mozjpeg Create JPEG files using mozJpeg
|
||||||
--maximizestrips Turn 1x4 strips to 2x2 strips
|
--maximizestrips Turn 1x4 strips to 2x2 strips
|
||||||
-d, --delete Delete source file(s) or a directory. It's not
|
-d, --delete Delete source file(s) or a directory. It's not recoverable.
|
||||||
recoverable.
|
|
||||||
|
|
||||||
CUSTOM PROFILE:
|
OUTPUT SETTINGS:
|
||||||
--customwidth=CUSTOMWIDTH
|
-o OUTPUT, --output OUTPUT
|
||||||
|
Output generated file to specified directory or file
|
||||||
|
-t TITLE, --title TITLE
|
||||||
|
Comic title [Default=filename or directory name]
|
||||||
|
-f FORMAT, --format FORMAT
|
||||||
|
Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB) [Default=Auto]
|
||||||
|
-b BATCHSPLIT, --batchsplit BATCHSPLIT
|
||||||
|
Split output into multiple files. 0: Don't split 1: Automatic mode 2: Consider every subdirectory as separate volume [Default=0]
|
||||||
|
|
||||||
|
CUSTOM PROFILE:
|
||||||
|
--customwidth CUSTOMWIDTH
|
||||||
Replace screen width provided by device profile
|
Replace screen width provided by device profile
|
||||||
--customheight=CUSTOMHEIGHT
|
--customheight CUSTOMHEIGHT
|
||||||
Replace screen height provided by device profile
|
Replace screen height provided by device profile
|
||||||
|
|
||||||
OTHER:
|
OTHER:
|
||||||
-h, --help Show this help message and exit
|
-h, --help Show this help message and exit
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Standalone `kcc-c2p.py` usage:
|
### Standalone `kcc-c2p.py` usage:
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: kcc-c2p [options] comic_folder
|
usage: kcc-c2p [options] [input]
|
||||||
|
|
||||||
Options:
|
MANDATORY:
|
||||||
MANDATORY:
|
input Full path to comic folder(s) to be processed. Separate multiple inputs with spaces.
|
||||||
-y HEIGHT, --height=HEIGHT
|
|
||||||
|
MAIN:
|
||||||
|
-y HEIGHT, --height HEIGHT
|
||||||
Height of the target device screen
|
Height of the target device screen
|
||||||
-i, --in-place Overwrite source directory
|
-i, --in-place Overwrite source directory
|
||||||
-m, --merge Combine every directory into a single image before
|
-m, --merge Combine every directory into a single image before splitting
|
||||||
splitting
|
|
||||||
|
|
||||||
OTHER:
|
OTHER:
|
||||||
-d, --debug Create debug file for every split image
|
-d, --debug Create debug file for every split image
|
||||||
-h, --help Show this help message and exit
|
-h, --help Show this help message and exit
|
||||||
```
|
```
|
||||||
|
|
||||||
## CREDITS
|
## CREDITS
|
||||||
|
|||||||
15
environment.yml
Normal file
15
environment.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
name: kcc
|
||||||
|
channels:
|
||||||
|
- conda-forge
|
||||||
|
- defaults
|
||||||
|
dependencies:
|
||||||
|
- python=3.8
|
||||||
|
- Pillow>=5.2.0
|
||||||
|
- psutil>=5.0.0
|
||||||
|
- python-slugify>=1.2.1
|
||||||
|
- raven>=6.0.0
|
||||||
|
- distro
|
||||||
|
- pip
|
||||||
|
- pip:
|
||||||
|
- mozjpeg-lossless-optimization>=1.1.2
|
||||||
|
- PyQt5>=5.6.0
|
||||||
@@ -19,8 +19,9 @@
|
|||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
if sys.version_info[0] != 3:
|
|
||||||
print('ERROR: This is Python 3 script!')
|
if sys.version_info < (3, 8, 0):
|
||||||
|
print('ERROR: This is a Python 3.8+ script!')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
from multiprocessing import freeze_support, set_start_method
|
from multiprocessing import freeze_support, set_start_method
|
||||||
|
|||||||
@@ -19,8 +19,9 @@
|
|||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
if sys.version_info[0] != 3:
|
|
||||||
print('ERROR: This is Python 3 script!')
|
if sys.version_info < (3, 8, 0):
|
||||||
|
print('ERROR: This is a Python 3.8+ script!')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
from multiprocessing import freeze_support, set_start_method
|
from multiprocessing import freeze_support, set_start_method
|
||||||
|
|||||||
5
kcc.py
5
kcc.py
@@ -19,8 +19,9 @@
|
|||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
if sys.version_info[0] != 3:
|
|
||||||
print('ERROR: This is Python 3 script!')
|
if sys.version_info < (3, 8, 0):
|
||||||
|
print('ERROR: This is a Python 3.8+ script!')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# OS specific workarounds
|
# OS specific workarounds
|
||||||
|
|||||||
@@ -16,11 +16,12 @@
|
|||||||
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
from urllib.request import urlretrieve
|
from urllib.request import urlretrieve, urlopen
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from shutil import move, rmtree
|
from shutil import move, rmtree
|
||||||
from subprocess import STDOUT, PIPE
|
from subprocess import STDOUT, PIPE
|
||||||
@@ -142,64 +143,27 @@ class VersionThread(QtCore.QThread):
|
|||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# TODO adapt with github releases
|
try:
|
||||||
pass
|
last_version_url = urlopen("https://api.github.com/repos/ciromattia/kcc/releases/latest")
|
||||||
|
data = last_version_url.read()
|
||||||
|
encoding = last_version_url.info().get_content_charset('utf-8')
|
||||||
|
json_parser = json.loads(data.decode(encoding))
|
||||||
|
|
||||||
# try:
|
html_url = json_parser["html_url"]
|
||||||
# XML = parse(urlopen(Request('https://kcc.iosphe.re/Version/',
|
latest_version = json_parser["tag_name"]
|
||||||
# headers={'User-Agent': 'KindleComicConverter/' + __version__})))
|
latest_version = re.sub(r'^v', "", latest_version)
|
||||||
# except Exception:
|
|
||||||
# return
|
if ("b" not in __version__ and StrictVersion(latest_version) > StrictVersion(__version__)) \
|
||||||
# latestVersion = XML.childNodes[0].getElementsByTagName('LatestVersion')[0].childNodes[0].toxml()
|
or ("b" in __version__
|
||||||
# if ("beta" not in __version__ and StrictVersion(latestVersion) > StrictVersion(__version__)) \
|
and StrictVersion(latest_version) >= StrictVersion(re.sub(r'b.*', '', __version__))):
|
||||||
# or ("beta" in __version__
|
MW.addMessage.emit('<a href="' + html_url + '"><b>The new version is available!</b></a>', 'warning',
|
||||||
# and StrictVersion(latestVersion) >= StrictVersion(re.sub(r'-beta.*', '', __version__))):
|
False)
|
||||||
# if sys.platform.startswith('win'):
|
except Exception:
|
||||||
# self.newVersion = latestVersion
|
return
|
||||||
# self.md5 = XML.childNodes[0].getElementsByTagName('MD5')[0].childNodes[0].toxml()
|
|
||||||
# MW.showDialog.emit('<b>New version released!</b> <a href="https://github.com/ciromattia/kcc/releases/">'
|
|
||||||
# 'See changelog.</a><br/><br/>Installed version: ' + __version__ +
|
|
||||||
# '<br/>Current version: ' + latestVersion +
|
|
||||||
# '<br/><br/>Would you like to start automatic update?', 'question')
|
|
||||||
# self.getNewVersion()
|
|
||||||
# else:
|
|
||||||
# MW.addMessage.emit('<a href="https://kcc.iosphe.re/">'
|
|
||||||
# '<b>The new version is available!</b></a> '
|
|
||||||
# '(<a href="https://github.com/ciromattia/kcc/releases/">'
|
|
||||||
# 'Changelog</a>)', 'warning', False)
|
|
||||||
|
|
||||||
def setAnswer(self, dialoganswer):
|
def setAnswer(self, dialoganswer):
|
||||||
self.answer = dialoganswer
|
self.answer = dialoganswer
|
||||||
|
|
||||||
def getNewVersion(self):
|
|
||||||
while self.answer is None:
|
|
||||||
sleep(1)
|
|
||||||
if self.answer == QtWidgets.QMessageBox.Yes:
|
|
||||||
try:
|
|
||||||
MW.modeConvert.emit(-1)
|
|
||||||
MW.progressBarTick.emit('Downloading update')
|
|
||||||
path = urlretrieve('https://kcc.iosphe.re/Windows/KindleComicConverter_win_' +
|
|
||||||
self.newVersion + '.exe', reporthook=self.getNewVersionTick)
|
|
||||||
if self.md5 != md5Checksum(path[0]):
|
|
||||||
raise Exception
|
|
||||||
move(path[0], path[0] + '.exe')
|
|
||||||
MW.hideProgressBar.emit()
|
|
||||||
MW.modeConvert.emit(1)
|
|
||||||
Popen(path[0] + '.exe /SP- /silent /noicons', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
|
||||||
MW.forceShutdown.emit()
|
|
||||||
except Exception:
|
|
||||||
MW.addMessage.emit('Failed to download the update!', 'warning', False)
|
|
||||||
MW.hideProgressBar.emit()
|
|
||||||
MW.modeConvert.emit(1)
|
|
||||||
|
|
||||||
def getNewVersionTick(self, size, blocksize, totalsize):
|
|
||||||
progress = int((size / (totalsize // blocksize)) * 100)
|
|
||||||
if size == 0:
|
|
||||||
MW.progressBarTick.emit('100')
|
|
||||||
if progress > self.barProgress:
|
|
||||||
self.barProgress = progress
|
|
||||||
MW.progressBarTick.emit('tick')
|
|
||||||
|
|
||||||
|
|
||||||
class ProgressThread(QtCore.QThread):
|
class ProgressThread(QtCore.QThread):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -255,7 +219,7 @@ class WorkerThread(QtCore.QThread):
|
|||||||
MW.modeConvert.emit(0)
|
MW.modeConvert.emit(0)
|
||||||
|
|
||||||
parser = comic2ebook.makeParser()
|
parser = comic2ebook.makeParser()
|
||||||
options, _ = parser.parse_args()
|
options = parser.parse_args()
|
||||||
argv = ''
|
argv = ''
|
||||||
currentJobs = []
|
currentJobs = []
|
||||||
|
|
||||||
@@ -506,7 +470,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||||
'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf);;All (*.*)')
|
'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf);;All (*.*)')
|
||||||
else:
|
else:
|
||||||
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, 'Comic (*.pdf);;All (*.*)')
|
fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath,
|
||||||
|
'Comic (*.pdf);;All (*.*)')
|
||||||
for fname in fnames[0]:
|
for fname in fnames[0]:
|
||||||
if fname != '':
|
if fname != '':
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
@@ -617,7 +582,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
|
|
||||||
def togglecroppingBox(self, value):
|
def togglecroppingBox(self, value):
|
||||||
if value:
|
if value:
|
||||||
GUI.croppingWidget.setVisible(True)
|
GUI.croppingWidget.setVisible(True)
|
||||||
else:
|
else:
|
||||||
GUI.croppingWidget.setVisible(False)
|
GUI.croppingWidget.setVisible(False)
|
||||||
self.changeCroppingPower(100) # 1.0
|
self.changeCroppingPower(100) # 1.0
|
||||||
@@ -731,8 +696,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.Ok)
|
QtWidgets.QMessageBox.critical(MW, 'KCC - Error', message, QtWidgets.QMessageBox.Ok)
|
||||||
elif kind == 'question':
|
elif kind == 'question':
|
||||||
GUI.versionCheck.setAnswer(QtWidgets.QMessageBox.question(MW, 'KCC - Question', message,
|
GUI.versionCheck.setAnswer(QtWidgets.QMessageBox.question(MW, 'KCC - Question', message,
|
||||||
QtWidgets.QMessageBox.Yes,
|
QtWidgets.QMessageBox.Yes,
|
||||||
QtWidgets.QMessageBox.No))
|
QtWidgets.QMessageBox.No))
|
||||||
|
|
||||||
def updateProgressbar(self, command):
|
def updateProgressbar(self, command):
|
||||||
if command == 'tick':
|
if command == 'tick':
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from argparse import ArgumentParser
|
||||||
from time import strftime, gmtime
|
from time import strftime, gmtime
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from glob import glob, escape
|
from glob import glob, escape
|
||||||
@@ -28,10 +29,9 @@ from stat import S_IWRITE, S_IREAD, S_IEXEC
|
|||||||
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
|
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||||
from tempfile import mkdtemp, gettempdir, TemporaryFile
|
from tempfile import mkdtemp, gettempdir, TemporaryFile
|
||||||
from shutil import move, copytree, rmtree, copyfile
|
from shutil import move, copytree, rmtree, copyfile
|
||||||
from optparse import OptionParser, OptionGroup
|
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from slugify import slugify as slugifyExt
|
from slugify import slugify as slugify_ext
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from subprocess import STDOUT, PIPE
|
from subprocess import STDOUT, PIPE
|
||||||
from psutil import Popen, virtual_memory, disk_usage
|
from psutil import Popen, virtual_memory, disk_usage
|
||||||
@@ -54,23 +54,23 @@ from . import __version__
|
|||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
global options
|
global options
|
||||||
parser = makeParser()
|
parser = makeParser()
|
||||||
optionstemplate, args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
if len(args) == 0:
|
options = copy(args)
|
||||||
|
if not argv or options.input == []:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
return 0
|
return 0
|
||||||
if sys.platform.startswith('win'):
|
if sys.platform.startswith('win'):
|
||||||
sources = set([source for arg in args for source in glob(escape(arg))])
|
sources = set([source for option in options.input for source in glob(escape(option))])
|
||||||
else:
|
else:
|
||||||
sources = set(args)
|
sources = set(options.input)
|
||||||
if len(sources) == 0:
|
if len(sources) == 0:
|
||||||
print('No matching files found.')
|
print('No matching files found.')
|
||||||
return 1
|
return 1
|
||||||
for source in sources:
|
for source in sources:
|
||||||
source = source.rstrip('\\').rstrip('/')
|
source = source.rstrip('\\').rstrip('/')
|
||||||
options = copy(optionstemplate)
|
options = copy(args)
|
||||||
options = checkOptions(options)
|
options = checkOptions(options)
|
||||||
if len(sources) > 1:
|
print('Working on ' + source + '...')
|
||||||
print('Working on ' + source + '...')
|
|
||||||
makeBook(source)
|
makeBook(source)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -546,7 +546,7 @@ def imgDirectoryProcessing(path):
|
|||||||
GUI.progressBarTick.emit(str(pagenumber))
|
GUI.progressBarTick.emit(str(pagenumber))
|
||||||
if len(work) > 0:
|
if len(work) > 0:
|
||||||
for i in work:
|
for i in work:
|
||||||
workerPool.apply_async(func=imgFileProcessing, args=(i, ), callback=imgFileProcessingTick)
|
workerPool.apply_async(func=imgFileProcessing, args=(i,), callback=imgFileProcessingTick)
|
||||||
workerPool.close()
|
workerPool.close()
|
||||||
workerPool.join()
|
workerPool.join()
|
||||||
if GUI and not GUI.conversionAlive:
|
if GUI and not GUI.conversionAlive:
|
||||||
@@ -910,9 +910,9 @@ def createNewTome():
|
|||||||
|
|
||||||
def slugify(value, isdir):
|
def slugify(value, isdir):
|
||||||
if isdir:
|
if isdir:
|
||||||
value = slugifyExt(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
|
value = slugify_ext(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
|
||||||
else:
|
else:
|
||||||
value = slugifyExt(value).strip('.')
|
value = slugify_ext(value).strip('.')
|
||||||
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -933,85 +933,84 @@ def makeZIP(zipfilename, basedir, isepub=False):
|
|||||||
|
|
||||||
|
|
||||||
def makeParser():
|
def makeParser():
|
||||||
psr = OptionParser(usage="Usage: kcc-c2e [options] comic_file|comic_folder", add_help_option=False)
|
psr = ArgumentParser(prog="kcc-c2e", usage="kcc-c2e [options] [input]", add_help=False)
|
||||||
|
|
||||||
mainOptions = OptionGroup(psr, "MAIN")
|
mandatory_options = psr.add_argument_group("MANDATORY")
|
||||||
processingOptions = OptionGroup(psr, "PROCESSING")
|
main_options = psr.add_argument_group("MAIN")
|
||||||
outputOptions = OptionGroup(psr, "OUTPUT SETTINGS")
|
processing_options = psr.add_argument_group("PROCESSING")
|
||||||
customProfileOptions = OptionGroup(psr, "CUSTOM PROFILE")
|
output_options = psr.add_argument_group("OUTPUT SETTINGS")
|
||||||
otherOptions = OptionGroup(psr, "OTHER")
|
custom_profile_options = psr.add_argument_group("CUSTOM PROFILE")
|
||||||
|
other_options = psr.add_argument_group("OTHER")
|
||||||
|
|
||||||
mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KV",
|
mandatory_options.add_argument("input", action="extend", nargs="*", default=None,
|
||||||
help="Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KPW5, KV, KO, "
|
help="Full path to comic folder or file(s) to be processed.")
|
||||||
"K11, KS, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoL, KoF, KoS, KoE)"
|
|
||||||
" [Default=KV]")
|
|
||||||
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
|
|
||||||
help="Manga style (right-to-left reading and splitting)")
|
|
||||||
mainOptions.add_option("-q", "--hq", action="store_true", dest="hq", default=False,
|
|
||||||
help="Try to increase the quality of magnification")
|
|
||||||
mainOptions.add_option("-2", "--two-panel", action="store_true", dest="autoscale", default=False,
|
|
||||||
help="Display two not four panels in Panel View mode")
|
|
||||||
mainOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
|
|
||||||
help="Webtoon processing mode"),
|
|
||||||
mainOptions.add_option("--targetsize", type="int", dest="targetsize", default=None,
|
|
||||||
help="the maximal size of output file in MB."
|
|
||||||
" [Default=100MB for webtoon and 400MB for others]")
|
|
||||||
|
|
||||||
outputOptions.add_option("-o", "--output", action="store", dest="output", default=None,
|
main_options.add_argument("-p", "--profile", action="store", dest="profile", default="KV",
|
||||||
help="Output generated file to specified directory or file")
|
help="Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KPW5, KV, KO, "
|
||||||
outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
|
"K11, KS, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoL, KoF, KoS, KoE)"
|
||||||
help="Comic title [Default=filename or directory name]")
|
" [Default=KV]")
|
||||||
outputOptions.add_option("-f", "--format", action="store", dest="format", default="Auto",
|
main_options.add_argument("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
|
||||||
help="Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB) "
|
help="Manga style (right-to-left reading and splitting)")
|
||||||
"[Default=Auto]")
|
main_options.add_argument("-q", "--hq", action="store_true", dest="hq", default=False,
|
||||||
outputOptions.add_option("-b", "--batchsplit", type="int", dest="batchsplit", default="0",
|
help="Try to increase the quality of magnification")
|
||||||
help="Split output into multiple files. 0: Don't split 1: Automatic mode "
|
main_options.add_argument("-2", "--two-panel", action="store_true", dest="autoscale", default=False,
|
||||||
"2: Consider every subdirectory as separate volume [Default=0]")
|
help="Display two not four panels in Panel View mode")
|
||||||
|
main_options.add_argument("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
|
||||||
|
help="Webtoon processing mode"),
|
||||||
|
main_options.add_argument("--ts", "--targetsize", type=int, dest="targetsize", default=None,
|
||||||
|
help="the maximal size of output file in MB."
|
||||||
|
" [Default=100MB for webtoon and 400MB for others]")
|
||||||
|
|
||||||
processingOptions.add_option("-n", "--noprocessing", action="store_true", dest="noprocessing", default=False,
|
output_options.add_argument("-o", "--output", action="store", dest="output", default=None,
|
||||||
help="Do not modify image and ignore any profil or processing option")
|
help="Output generated file to specified directory or file")
|
||||||
processingOptions.add_option("-u", "--upscale", action="store_true", dest="upscale", default=False,
|
output_options.add_argument("-t", "--title", action="store", dest="title", default="defaulttitle",
|
||||||
help="Resize images smaller than device's resolution")
|
help="Comic title [Default=filename or directory name]")
|
||||||
processingOptions.add_option("-s", "--stretch", action="store_true", dest="stretch", default=False,
|
output_options.add_argument("-f", "--format", action="store", dest="format", default="Auto",
|
||||||
help="Stretch images to device's resolution")
|
help="Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB) "
|
||||||
processingOptions.add_option("-r", "--splitter", type="int", dest="splitter", default="0",
|
"[Default=Auto]")
|
||||||
help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]")
|
output_options.add_argument("-b", "--batchsplit", type=int, dest="batchsplit", default="0",
|
||||||
processingOptions.add_option("-g", "--gamma", type="float", dest="gamma", default="0.0",
|
help="Split output into multiple files. 0: Don't split 1: Automatic mode "
|
||||||
help="Apply gamma correction to linearize the image [Default=Auto]")
|
"2: Consider every subdirectory as separate volume [Default=0]")
|
||||||
processingOptions.add_option("-c", "--cropping", type="int", dest="cropping", default="2",
|
|
||||||
help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
|
|
||||||
processingOptions.add_option("--cp", "--croppingpower", type="float", dest="croppingp", default="1.0",
|
|
||||||
help="Set cropping power [Default=1.0]")
|
|
||||||
processingOptions.add_option("--cm", "--croppingminimum", type="float", dest="croppingm", default="0.0",
|
|
||||||
help="Set cropping minimum area ratio [Default=0.0]")
|
|
||||||
processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
|
|
||||||
help="Disable autodetection and force black borders")
|
|
||||||
processingOptions.add_option("--whiteborders", action="store_true", dest="white_borders", default=False,
|
|
||||||
help="Disable autodetection and force white borders")
|
|
||||||
processingOptions.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False,
|
|
||||||
help="Don't convert images to grayscale")
|
|
||||||
processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
|
|
||||||
help="Create PNG files instead JPEG")
|
|
||||||
processingOptions.add_option("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
|
|
||||||
help="Create JPEG files using mozJpeg")
|
|
||||||
processingOptions.add_option("--maximizestrips", action="store_true", dest="maximizestrips", default=False,
|
|
||||||
help="Turn 1x4 strips to 2x2 strips")
|
|
||||||
processingOptions.add_option("-d", "--delete", action="store_true", dest="delete", default=False,
|
|
||||||
help="Delete source file(s) or a directory. It's not recoverable.")
|
|
||||||
|
|
||||||
customProfileOptions.add_option("--customwidth", type="int", dest="customwidth", default=0,
|
processing_options.add_argument("-n", "--noprocessing", action="store_true", dest="noprocessing", default=False,
|
||||||
help="Replace screen width provided by device profile")
|
help="Do not modify image and ignore any profil or processing option")
|
||||||
customProfileOptions.add_option("--customheight", type="int", dest="customheight", default=0,
|
processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False,
|
||||||
help="Replace screen height provided by device profile")
|
help="Resize images smaller than device's resolution")
|
||||||
|
processing_options.add_argument("-s", "--stretch", action="store_true", dest="stretch", default=False,
|
||||||
|
help="Stretch images to device's resolution")
|
||||||
|
processing_options.add_argument("-r", "--splitter", type=int, dest="splitter", default="0",
|
||||||
|
help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]")
|
||||||
|
processing_options.add_argument("-g", "--gamma", type=float, dest="gamma", default="0.0",
|
||||||
|
help="Apply gamma correction to linearize the image [Default=Auto]")
|
||||||
|
processing_options.add_argument("-c", "--cropping", type=int, dest="cropping", default="2",
|
||||||
|
help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
|
||||||
|
processing_options.add_argument("--cp", "--croppingpower", type=float, dest="croppingp", default="1.0",
|
||||||
|
help="Set cropping power [Default=1.0]")
|
||||||
|
processing_options.add_argument("--cm", "--croppingminimum", type=float, dest="croppingm", default="0.0",
|
||||||
|
help="Set cropping minimum area ratio [Default=0.0]")
|
||||||
|
processing_options.add_argument("--blackborders", action="store_true", dest="black_borders", default=False,
|
||||||
|
help="Disable autodetection and force black borders")
|
||||||
|
processing_options.add_argument("--whiteborders", action="store_true", dest="white_borders", default=False,
|
||||||
|
help="Disable autodetection and force white borders")
|
||||||
|
processing_options.add_argument("--forcecolor", action="store_true", dest="forcecolor", default=False,
|
||||||
|
help="Don't convert images to grayscale")
|
||||||
|
processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False,
|
||||||
|
help="Create PNG files instead JPEG")
|
||||||
|
processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
|
||||||
|
help="Create JPEG files using mozJpeg")
|
||||||
|
processing_options.add_argument("--maximizestrips", action="store_true", dest="maximizestrips", default=False,
|
||||||
|
help="Turn 1x4 strips to 2x2 strips")
|
||||||
|
processing_options.add_argument("-d", "--delete", action="store_true", dest="delete", default=False,
|
||||||
|
help="Delete source file(s) or a directory. It's not recoverable.")
|
||||||
|
|
||||||
otherOptions.add_option("-h", "--help", action="help",
|
custom_profile_options.add_argument("--customwidth", type=int, dest="customwidth", default=0,
|
||||||
help="Show this help message and exit")
|
help="Replace screen width provided by device profile")
|
||||||
|
custom_profile_options.add_argument("--customheight", type=int, dest="customheight", default=0,
|
||||||
|
help="Replace screen height provided by device profile")
|
||||||
|
|
||||||
|
other_options.add_argument("-h", "--help", action="help",
|
||||||
|
help="Show this help message and exit")
|
||||||
|
|
||||||
psr.add_option_group(mainOptions)
|
|
||||||
psr.add_option_group(outputOptions)
|
|
||||||
psr.add_option_group(processingOptions)
|
|
||||||
psr.add_option_group(customProfileOptions)
|
|
||||||
psr.add_option_group(otherOptions)
|
|
||||||
return psr
|
return psr
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from argparse import ArgumentParser
|
||||||
from shutil import rmtree, copytree, move
|
from shutil import rmtree, copytree, move
|
||||||
from optparse import OptionParser, OptionGroup
|
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
from PIL import Image, ImageChops, ImageOps, ImageDraw
|
from PIL import Image, ImageChops, ImageOps, ImageDraw
|
||||||
from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace
|
from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace
|
||||||
@@ -102,7 +102,7 @@ def splitImage(work):
|
|||||||
opt = work[2]
|
opt = work[2]
|
||||||
filePath = os.path.join(path, name)
|
filePath = os.path.join(path, name)
|
||||||
Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
|
Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
|
||||||
Image.MAX_IMAGE_PIXELS = 1000000000
|
Image.MAX_IMAGE_PIXELS = 1000000000
|
||||||
imgOrg = Image.open(filePath).convert('RGB')
|
imgOrg = Image.open(filePath).convert('RGB')
|
||||||
imgProcess = Image.open(filePath).convert('1')
|
imgProcess = Image.open(filePath).convert('1')
|
||||||
widthImg, heightImg = imgOrg.size
|
widthImg, heightImg = imgOrg.size
|
||||||
@@ -116,7 +116,7 @@ def splitImage(work):
|
|||||||
panelDetected = False
|
panelDetected = False
|
||||||
panels = []
|
panels = []
|
||||||
while yWork < heightImg:
|
while yWork < heightImg:
|
||||||
tmpImg = imgProcess.crop([4, yWork, widthImg-4, yWork + 4])
|
tmpImg = imgProcess.crop((4, yWork, widthImg-4, yWork + 4))
|
||||||
solid = detectSolid(tmpImg)
|
solid = detectSolid(tmpImg)
|
||||||
if not solid and not panelDetected:
|
if not solid and not panelDetected:
|
||||||
panelDetected = True
|
panelDetected = True
|
||||||
@@ -149,7 +149,7 @@ def splitImage(work):
|
|||||||
|
|
||||||
if opt.debug:
|
if opt.debug:
|
||||||
for panel in panelsProcessed:
|
for panel in panelsProcessed:
|
||||||
draw.rectangle([(0, panel[0]), (widthImg, panel[1])], (0, 255, 0, 128), (0, 0, 255, 255))
|
draw.rectangle(((0, panel[0]), (widthImg, panel[1])), (0, 255, 0, 128), (0, 0, 255, 255))
|
||||||
debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg)
|
debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg)
|
||||||
debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG')
|
debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG')
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ def splitImage(work):
|
|||||||
if pageHeight > 15:
|
if pageHeight > 15:
|
||||||
newPage = Image.new('RGB', (widthImg, pageHeight))
|
newPage = Image.new('RGB', (widthImg, pageHeight))
|
||||||
for panel in page:
|
for panel in page:
|
||||||
panelImg = imgOrg.crop([0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]])
|
panelImg = imgOrg.crop((0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]))
|
||||||
newPage.paste(panelImg, (0, targetHeight))
|
newPage.paste(panelImg, (0, targetHeight))
|
||||||
targetHeight += panelsProcessed[panel][2]
|
targetHeight += panelsProcessed[panel][2]
|
||||||
newPage.save(os.path.join(path, os.path.splitext(name)[0] + '-' + str(pageNumber) + '.png'), 'PNG')
|
newPage.save(os.path.join(path, os.path.splitext(name)[0] + '-' + str(pageNumber) + '.png'), 'PNG')
|
||||||
@@ -193,97 +193,100 @@ def splitImage(work):
|
|||||||
|
|
||||||
|
|
||||||
def main(argv=None, qtgui=None):
|
def main(argv=None, qtgui=None):
|
||||||
global options, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput
|
global args, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput
|
||||||
parser = OptionParser(usage="Usage: kcc-c2p [options] comic_folder", add_help_option=False)
|
parser = ArgumentParser(prog="kcc-c2p", usage="kcc-c2p [options] [input]", add_help=False)
|
||||||
mainOptions = OptionGroup(parser, "MANDATORY")
|
|
||||||
otherOptions = OptionGroup(parser, "OTHER")
|
mandatory_options = parser.add_argument_group("MANDATORY")
|
||||||
mainOptions.add_option("-y", "--height", type="int", dest="height", default=0,
|
main_options = parser.add_argument_group("MAIN")
|
||||||
help="Height of the target device screen")
|
other_options = parser.add_argument_group("OTHER")
|
||||||
mainOptions.add_option("-i", "--in-place", action="store_true", dest="inPlace", default=False,
|
mandatory_options.add_argument("input", action="extend", nargs="*", default=None,
|
||||||
help="Overwrite source directory")
|
help="Full path to comic folder(s) to be processed. Separate multiple inputs"
|
||||||
mainOptions.add_option("-m", "--merge", action="store_true", dest="merge", default=False,
|
" with spaces.")
|
||||||
help="Combine every directory into a single image before splitting")
|
main_options.add_argument("-y", "--height", type=int, dest="height", default=0,
|
||||||
otherOptions.add_option("-d", "--debug", action="store_true", dest="debug", default=False,
|
help="Height of the target device screen")
|
||||||
help="Create debug file for every split image")
|
main_options.add_argument("-i", "--in-place", action="store_true", dest="inPlace", default=False,
|
||||||
otherOptions.add_option("-h", "--help", action="help",
|
help="Overwrite source directory")
|
||||||
help="Show this help message and exit")
|
main_options.add_argument("-m", "--merge", action="store_true", dest="merge", default=False,
|
||||||
parser.add_option_group(mainOptions)
|
help="Combine every directory into a single image before splitting")
|
||||||
parser.add_option_group(otherOptions)
|
other_options.add_argument("-d", "--debug", action="store_true", dest="debug", default=False,
|
||||||
options, args = parser.parse_args(argv)
|
help="Create debug file for every split image")
|
||||||
|
other_options.add_argument("-h", "--help", action="help",
|
||||||
|
help="Show this help message and exit")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
if qtgui:
|
if qtgui:
|
||||||
GUI = qtgui
|
GUI = qtgui
|
||||||
else:
|
else:
|
||||||
GUI = None
|
GUI = None
|
||||||
if len(args) != 1:
|
if not argv or args.input == []:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
return 1
|
return 1
|
||||||
if options.height > 0:
|
if args.height > 0:
|
||||||
options.sourceDir = args[0]
|
for sourceDir in args.input:
|
||||||
options.targetDir = args[0] + "-Splitted"
|
targetDir = sourceDir + "-Splitted"
|
||||||
if os.path.isdir(options.sourceDir):
|
if os.path.isdir(sourceDir):
|
||||||
rmtree(options.targetDir, True)
|
rmtree(targetDir, True)
|
||||||
copytree(options.sourceDir, options.targetDir)
|
copytree(sourceDir, targetDir)
|
||||||
work = []
|
work = []
|
||||||
pagenumber = 1
|
pagenumber = 1
|
||||||
splitWorkerOutput = []
|
splitWorkerOutput = []
|
||||||
splitWorkerPool = Pool(maxtasksperchild=10)
|
splitWorkerPool = Pool(maxtasksperchild=10)
|
||||||
if options.merge:
|
if args.merge:
|
||||||
print("Merging images...")
|
print("Merging images...")
|
||||||
directoryNumer = 1
|
directoryNumer = 1
|
||||||
mergeWork = []
|
mergeWork = []
|
||||||
mergeWorkerOutput = []
|
mergeWorkerOutput = []
|
||||||
mergeWorkerPool = Pool(maxtasksperchild=10)
|
mergeWorkerPool = Pool(maxtasksperchild=10)
|
||||||
mergeWork.append([options.targetDir])
|
mergeWork.append([targetDir])
|
||||||
for root, dirs, files in os.walk(options.targetDir, False):
|
for root, dirs, files in os.walk(targetDir, False):
|
||||||
dirs, files = walkSort(dirs, files)
|
dirs, files = walkSort(dirs, files)
|
||||||
for directory in dirs:
|
for directory in dirs:
|
||||||
directoryNumer += 1
|
directoryNumer += 1
|
||||||
mergeWork.append([os.path.join(root, directory)])
|
mergeWork.append([os.path.join(root, directory)])
|
||||||
|
if GUI:
|
||||||
|
GUI.progressBarTick.emit('Combining images')
|
||||||
|
GUI.progressBarTick.emit(str(directoryNumer))
|
||||||
|
for i in mergeWork:
|
||||||
|
mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick)
|
||||||
|
mergeWorkerPool.close()
|
||||||
|
mergeWorkerPool.join()
|
||||||
|
if GUI and not GUI.conversionAlive:
|
||||||
|
rmtree(targetDir, True)
|
||||||
|
raise UserWarning("Conversion interrupted.")
|
||||||
|
if len(mergeWorkerOutput) > 0:
|
||||||
|
rmtree(targetDir, True)
|
||||||
|
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
|
||||||
|
mergeWorkerOutput[0][1])
|
||||||
|
print("Splitting images...")
|
||||||
|
for root, _, files in os.walk(targetDir, False):
|
||||||
|
for name in files:
|
||||||
|
if getImageFileName(name) is not None:
|
||||||
|
pagenumber += 1
|
||||||
|
work.append([root, name, args])
|
||||||
|
else:
|
||||||
|
os.remove(os.path.join(root, name))
|
||||||
if GUI:
|
if GUI:
|
||||||
GUI.progressBarTick.emit('Combining images')
|
GUI.progressBarTick.emit('Splitting images')
|
||||||
GUI.progressBarTick.emit(str(directoryNumer))
|
GUI.progressBarTick.emit(str(pagenumber))
|
||||||
for i in mergeWork:
|
GUI.progressBarTick.emit('tick')
|
||||||
mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick)
|
if len(work) > 0:
|
||||||
mergeWorkerPool.close()
|
for i in work:
|
||||||
mergeWorkerPool.join()
|
splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImageTick)
|
||||||
if GUI and not GUI.conversionAlive:
|
splitWorkerPool.close()
|
||||||
rmtree(options.targetDir, True)
|
splitWorkerPool.join()
|
||||||
raise UserWarning("Conversion interrupted.")
|
if GUI and not GUI.conversionAlive:
|
||||||
if len(mergeWorkerOutput) > 0:
|
rmtree(targetDir, True)
|
||||||
rmtree(options.targetDir, True)
|
raise UserWarning("Conversion interrupted.")
|
||||||
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
|
if len(splitWorkerOutput) > 0:
|
||||||
mergeWorkerOutput[0][1])
|
rmtree(targetDir, True)
|
||||||
print("Splitting images...")
|
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
|
||||||
for root, _, files in os.walk(options.targetDir, False):
|
splitWorkerOutput[0][1])
|
||||||
for name in files:
|
if args.inPlace:
|
||||||
if getImageFileName(name) is not None:
|
rmtree(sourceDir)
|
||||||
pagenumber += 1
|
move(targetDir, sourceDir)
|
||||||
work.append([root, name, options])
|
else:
|
||||||
else:
|
rmtree(targetDir, True)
|
||||||
os.remove(os.path.join(root, name))
|
raise UserWarning("Source directory is empty.")
|
||||||
if GUI:
|
|
||||||
GUI.progressBarTick.emit('Splitting images')
|
|
||||||
GUI.progressBarTick.emit(str(pagenumber))
|
|
||||||
GUI.progressBarTick.emit('tick')
|
|
||||||
if len(work) > 0:
|
|
||||||
for i in work:
|
|
||||||
splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImageTick)
|
|
||||||
splitWorkerPool.close()
|
|
||||||
splitWorkerPool.join()
|
|
||||||
if GUI and not GUI.conversionAlive:
|
|
||||||
rmtree(options.targetDir, True)
|
|
||||||
raise UserWarning("Conversion interrupted.")
|
|
||||||
if len(splitWorkerOutput) > 0:
|
|
||||||
rmtree(options.targetDir, True)
|
|
||||||
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
|
|
||||||
splitWorkerOutput[0][1])
|
|
||||||
if options.inPlace:
|
|
||||||
rmtree(options.sourceDir)
|
|
||||||
move(options.targetDir, options.sourceDir)
|
|
||||||
else:
|
else:
|
||||||
rmtree(options.targetDir, True)
|
raise UserWarning("Provided input is not a directory.")
|
||||||
raise UserWarning("Source directory is empty.")
|
|
||||||
else:
|
|
||||||
raise UserWarning("Provided path is not a directory.")
|
|
||||||
else:
|
else:
|
||||||
raise UserWarning("Target height is not set.")
|
raise UserWarning("Target height is not set.")
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ 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 don\'t exist.')
|
raise OSError('Target directory doesn\'t exist.')
|
||||||
process = Popen('7z x -y -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' + targetdir + '" "' +
|
process = Popen('7z x -y -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' + targetdir + '" "' +
|
||||||
self.filepath + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
self.filepath + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
|
||||||
process.communicate()
|
process.communicate()
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ import mozjpeg_lossless_optimization
|
|||||||
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
|
||||||
from .shared import md5Checksum
|
from .shared import md5Checksum
|
||||||
|
|
||||||
|
# 0.045 was determined by
|
||||||
|
# 1200 / 824 = 1.456 (Kindle DX resolution)
|
||||||
|
# 2250 / 1500 = 1.5 (Typical manga page resolution)
|
||||||
|
# 1.5 - 1.456 < 0.045
|
||||||
|
# 0.045 / 1.5 = 0.03 (So maximum 3% of is cropped)
|
||||||
|
AUTO_CROP_THRESHOLD = 0.045
|
||||||
|
|
||||||
|
|
||||||
class ProfileData:
|
class ProfileData:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -306,36 +313,32 @@ class ComicPage:
|
|||||||
self.image = self.image.quantize(palette=palImg)
|
self.image = self.image.quantize(palette=palImg)
|
||||||
|
|
||||||
def resizeImage(self):
|
def resizeImage(self):
|
||||||
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
ratio_device = float(self.size[1]) / float(self.size[0])
|
||||||
method = Image.Resampling.BICUBIC
|
ratio_image = float(self.image.size[1]) / float(self.image.size[0])
|
||||||
else:
|
method = self.resize_method()
|
||||||
method = Image.Resampling.LANCZOS
|
|
||||||
if self.opt.stretch:
|
if self.opt.stretch:
|
||||||
# if self.opt.stretch or (self.opt.kfx and ('-KCC-B' in self.targetPath or '-KCC-C' in self.targetPath)):
|
|
||||||
self.image = self.image.resize(self.size, method)
|
self.image = self.image.resize(self.size, method)
|
||||||
elif self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1] and not self.opt.upscale:
|
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
||||||
if self.opt.format == 'CBZ' or self.opt.kfx:
|
if self.opt.format == 'CBZ' or self.opt.kfx:
|
||||||
borderw = int((self.size[0] - self.image.size[0]) / 2)
|
borderw = int((self.size[0] - self.image.size[0]) / 2)
|
||||||
borderh = int((self.size[1] - self.image.size[1]) / 2)
|
borderh = int((self.size[1] - self.image.size[1]) / 2)
|
||||||
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
|
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
|
||||||
if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]:
|
if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]:
|
||||||
self.image = ImageOps.fit(self.image, self.size, method=Image.Resampling.BICUBIC, centering=(0.5, 0.5))
|
self.image = ImageOps.fit(self.image, self.size, method=method)
|
||||||
else:
|
else: # if image bigger than device resolution or smaller with upscaling
|
||||||
if self.opt.format == 'CBZ' or self.opt.kfx:
|
if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
||||||
ratioDev = float(self.size[0]) / float(self.size[1])
|
self.image = ImageOps.fit(self.image, self.size, method=method)
|
||||||
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
|
elif self.opt.format == 'CBZ' or self.opt.kfx:
|
||||||
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
|
||||||
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=self.fill)
|
|
||||||
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
|
|
||||||
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
|
|
||||||
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.fill)
|
|
||||||
self.image = ImageOps.fit(self.image, self.size, method=method, centering=(0.5, 0.5))
|
|
||||||
else:
|
else:
|
||||||
hpercent = self.size[1] / float(self.image.size[1])
|
self.image = ImageOps.contain(self.image, self.size, method=method)
|
||||||
wsize = int((float(self.image.size[0]) * float(hpercent)))
|
|
||||||
self.image = self.image.resize((wsize, self.size[1]), method)
|
def resize_method(self):
|
||||||
if self.image.size[0] > self.size[0] or self.image.size[1] > self.size[1]:
|
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
||||||
self.image.thumbnail(self.size, Image.Resampling.LANCZOS)
|
method = Image.Resampling.BICUBIC
|
||||||
|
else:
|
||||||
|
method = Image.Resampling.LANCZOS
|
||||||
|
return method
|
||||||
|
|
||||||
def getBoundingBox(self, tmptmg):
|
def getBoundingBox(self, tmptmg):
|
||||||
min_margin = [int(0.005 * i + 0.5) for i in tmptmg.size]
|
min_margin = [int(0.005 * i + 0.5) for i in tmptmg.size]
|
||||||
|
|||||||
Reference in New Issue
Block a user