1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-18 15:08:48 +00:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Alex Xu
d2981c0ceb Bump version to 9.3.0 2025-11-09 18:07:50 -08:00
José Cerezo
8268552ac7 Optimized Docker image (#1155)
* Optimized Docker image

* Divided Dockerfile into two images

* Fixed dockerfile path

* Updated workflows

* Added remaining packages in Dockerfile-base

* Updated workflows
2025-11-09 15:58:55 -08:00
27 changed files with 365 additions and 465 deletions

View File

@@ -38,7 +38,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v5
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL

View File

@@ -0,0 +1,45 @@
name: Build and Publish Base Docker Image
on:
workflow_dispatch:
push:
branches:
- master
paths:
- 'requirements-docker.txt'
- 'Dockerfile-base'
jobs:
build_and_publish_base_image:
runs-on: ubuntu-latest
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set Release Date
id: release_date
run: |
echo "release_date=$(date --rfc-3339=date)" >> $GITHUB_OUTPUT
- name: 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

View File

@@ -1,4 +1,4 @@
name: Build and Publish Docker Image name: Build and publish final image
on: on:
workflow_dispatch: workflow_dispatch:
@@ -6,7 +6,7 @@ on:
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,14 +15,13 @@ on:
- 'LICENSE' - 'LICENSE'
- '.gitattributes' - '.gitattributes'
- '.gitignore' - '.gitignore'
- 'requirements-docker.txt'
- 'Dockerfile-base'
jobs: jobs:
build_and_publish_base_image: build_and_publish_image:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout
uses: actions/checkout@v6
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@@ -36,32 +35,17 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Set Release Date
id: release_date
run: |
echo "release_date=$(date --rfc-3339=date)" >> $GITHUB_OUTPUT
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
images: ghcr.io/${{ github.repository_owner }}/kcc images: ghcr.io/${{ github.repository_owner }}/kcc
# Always creates the "latest" tag
flavor: |
latest=true
tags: |
type=ref,event=tag
type=raw,value=${{ steps.release_date.outputs.release_date }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm64,linux/arm/v7
context: .
push: true push: true
tags: | tags: ${{ steps.meta.outputs.tags }}
${{ steps.meta.outputs.tags }} cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/kcc:final-cache
cache-from: | cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/kcc:final-cache,mode=max
type=registry,ref=ghcr.io/ciromattia/kcc:cache
type=registry,ref=ghcr.io/${{ github.repository_owner }}/kcc:cache
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/kcc:cache,mode=max

View File

@@ -25,7 +25,7 @@ jobs:
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
@@ -59,7 +59,7 @@ jobs:
env: env:
UPDATE_INFO: gh-releases-zsync|ciromattia|kcc|latest|*x86_64.AppImage.zsync UPDATE_INFO: gh-releases-zsync|ciromattia|kcc|latest|*x86_64.AppImage.zsync
- name: upload artifact - name: upload artifact
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v4
with: with:
name: AppImage name: AppImage
path: './*.AppImage*' path: './*.AppImage*'

View File

@@ -25,12 +25,10 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
os: [ macos-15-intel, macos-14 ] os: [ macos-13, macos-14 ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
MACOSX_DEPLOYMENT_TARGET: '14.0'
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
@@ -80,7 +78,7 @@ jobs:
run: | run: |
python setup.py build_binary python setup.py build_binary
- name: upload build - name: upload build
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v4
with: with:
name: mac-os-build-${{ runner.arch }} name: mac-os-build-${{ runner.arch }}
path: dist/*.dmg path: dist/*.dmg

View File

@@ -23,7 +23,7 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
os: [ macos-15-intel ] os: [ macos-13 ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env: env:
# We need the official Python, because the GA ones only support newer macOS versions # We need the official Python, because the GA ones only support newer macOS versions
@@ -31,7 +31,7 @@ jobs:
PYTHON_VERSION: 3.11.9 PYTHON_VERSION: 3.11.9
MACOSX_DEPLOYMENT_TARGET: '10.14' MACOSX_DEPLOYMENT_TARGET: '10.14'
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- name: Get Python - name: Get Python
run: curl https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg -o "python.pkg" run: curl https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg -o "python.pkg"
- name: Install Python - name: Install Python
@@ -51,7 +51,7 @@ jobs:
run: | run: |
python3 setup.py build_binary python3 setup.py build_binary
- name: upload build - name: upload build
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v4
with: with:
name: osx-build-${{ runner.arch }} name: osx-build-${{ runner.arch }}
path: dist/*.dmg path: dist/*.dmg

View File

@@ -35,7 +35,7 @@ jobs:
command: build_c2p command: build_c2p
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
@@ -53,12 +53,12 @@ jobs:
python setup.py ${{ matrix.command }} 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@v6 uses: actions/upload-artifact@v4
with: with:
name: windows-build-${{ matrix.entry }} 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@v2.0 uses: signpath/github-action-submit-signing-request@v1.3
if: ${{ github.repository == 'ciromattia/kcc' }} if: ${{ github.repository == 'ciromattia/kcc' }}
with: with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'

View File

@@ -27,7 +27,7 @@ jobs:
env: env:
WINDOWS_7: 1 WINDOWS_7: 1
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
@@ -46,7 +46,7 @@ jobs:
python setup.py build_binary python setup.py build_binary
- name: upload-unsigned-artifact - name: upload-unsigned-artifact
id: upload-unsigned-artifact id: upload-unsigned-artifact
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v4
with: with:
name: windows7-build name: windows7-build
path: dist/*.exe path: dist/*.exe

View File

@@ -1,77 +1,20 @@
# STAGE 1: BUILDER FROM ghcr.io/ciromattia/kcc:base-latest
# Contains all build tools and dev dependencies, will be discarded
FROM python:3.13-slim-bullseye AS builder
# Install system dependencies
RUN set -x && \
BUILD_DEPS="build-essential cmake libffi-dev libfreetype6-dev libfontconfig1-dev libpng-dev libjpeg-dev libssl-dev libxft-dev make python3-dev python3-setuptools python3-wheel" && \
RUNTIME_DEPS="bash ca-certificates chrpath locales locales-all libfreetype6 libfontconfig1 p7zip-full python3 python3-pip libgl1" && \
DEBIAN_FRONTEND=noninteractive apt-get update -y && \
apt-get install -y --no-install-recommends ${BUILD_DEPS} ${RUNTIME_DEPS}
RUN \
set -x && \
python -m venv /opt/venv && \
. /opt/venv/bin/activate && \
pip install --upgrade pip
# Install numpy first, as it is unlikely to change and takes too long to compile
RUN \
set -x && \
. /opt/venv/bin/activate && \
pip install --no-cache-dir numpy==2.3.4
# Install PyMuPDF separately, as it is likely to change but still takes too long to compile
RUN \
set -x && \
. /opt/venv/bin/activate && \
pip install --no-cache-dir PyMuPDF==1.26.6
# Install Python dependencies using virtual environment
COPY requirements-docker.txt .
RUN \
set -x && \
. /opt/venv/bin/activate && \
pip install --no-cache-dir -r requirements-docker.txt
# STAGE 2: FINAL
# Clean, small and secure image with only runtime dependencies
FROM python:3.13-slim-bullseye
# Install runtime dependencies only
RUN \
set -x && \
DEBIAN_FRONTEND=noninteractive apt-get update -y && \
apt-get install -y --no-install-recommends p7zip-full && \
rm -rf /var/lib/apt/lists/*
# Copy artifacts from builder
COPY --from=builder /opt/venv /opt/venv
COPY . /opt/kcc/ COPY . /opt/kcc/
WORKDIR /opt/kcc
ENV PATH="/opt/venv/bin:$PATH"
# Setup executable and version file # Setup executable and version file
RUN \ RUN \
chmod +x /opt/kcc/entrypoint.sh && \ chmod +x /opt/kcc/entrypoint.sh && \
ln -s /opt/kcc/kcc-c2e.py /usr/local/bin/c2e && \ 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/kcc-c2p.py /usr/local/bin/c2p && \
ln -s /opt/kcc/entrypoint.sh /usr/local/bin/entrypoint && \ ln -s /opt/kcc/entrypoint.sh /usr/local/bin/entrypoint && \
ln -s /opt/kcc/kindlegen/kindlegen /usr/local/bin/kindlegen && \
cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION cat /opt/kcc/kindlecomicconverter/__init__.py | grep version | awk '{print $3}' | sed "s/'//g" > /IMAGE_VERSION
LABEL com.kcc.name="Kindle Comic Converter" \ LABEL com.kcc.name="Kindle Comic Converter" \
com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi" \ com.kcc.author="Ciro Mattia Gonano, Paweł Jastrzębski and Darodi" \
org.opencontainers.image.title="Kindle Comic Converter" \
org.opencontainers.image.description='Kindle Comic Converter' \ org.opencontainers.image.description='Kindle Comic Converter' \
org.opencontainers.image.documentation='https://github.com/ciromattia/kcc' \
org.opencontainers.image.source='https://github.com/ciromattia/kcc' \ org.opencontainers.image.source='https://github.com/ciromattia/kcc' \
org.opencontainers.image.authors='Darodi and José Cerezo' \ org.opencontainers.image.title="Kindle Comic Converter"
org.opencontainers.image.url='https://github.com/ciromattia/kcc' \
org.opencontainers.image.vendor='ciromattia' \
org.opencontainers.image.licenses='ISC'
ENTRYPOINT ["entrypoint"] ENTRYPOINT ["entrypoint"]
CMD ["-h"] CMD ["-h"]

44
Dockerfile-base Normal file
View File

@@ -0,0 +1,44 @@
# STAGE 1: BUILDER
# Contains all build tools and dev dependencies, will be discarded
FROM python:3.13-slim-bullseye AS builder
ARG TARGETARCH
# Install system dependencies
RUN set -x && \
BUILD_DEPS="build-essential cmake libffi-dev libfreetype6-dev libfontconfig1-dev libpng-dev libjpeg-dev libssl-dev libxft-dev make python3-dev python3-setuptools python3-wheel" && \
RUNTIME_DEPS="bash ca-certificates chrpath locales locales-all libfreetype6 libfontconfig1 p7zip-full python3 python3-pip unrar-free libgl1" && \
DEBIAN_FRONTEND=noninteractive apt-get update -y && \
apt-get install -y --no-install-recommends ${BUILD_DEPS} ${RUNTIME_DEPS}
# Install Python dependencies using virtual environment
COPY requirements-docker.txt .
RUN \
set -x && \
python -m venv /opt/venv && \
. /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/*
# Copy artifacts from builder
COPY --from=builder /opt/venv /opt/venv
WORKDIR /opt/kcc
ENV PATH="/opt/venv/bin:$PATH"
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 Base Image' \
org.opencontainers.image.source='https://github.com/ciromattia/kcc' \
org.opencontainers.image.title="Kindle Comic Converter Base Image"

View File

@@ -7,7 +7,7 @@
[![Github All Releases](https://img.shields.io/github/downloads/ciromattia/kcc/total.svg)](https://github.com/ciromattia/kcc/releases) [![Github All Releases](https://img.shields.io/github/downloads/ciromattia/kcc/total.svg)](https://github.com/ciromattia/kcc/releases)
**Kindle Comic Converter** optimizes black & white (or color) comics and manga for E-ink ereaders **Kindle Comic Converter** optimizes black & white comics and manga for E-ink ereaders
like Kindle, Kobo, ReMarkable, and more. like Kindle, Kobo, ReMarkable, and more.
Pages display in fullscreen without margins, Pages display in fullscreen without margins,
with proper fixed layout support. with proper fixed layout support.
@@ -26,7 +26,7 @@ which have different requirements than normal LCD screens.
Combining that with downscaling to your specific device's screen resolution Combining that with downscaling to your specific device's screen resolution
can result in filesize reductions of hundreds of MB per volume with no visible quality loss on eink. can result in filesize reductions of hundreds of MB per volume with no visible quality loss on eink.
This can also improve battery life, page turn speed, and general performance This can also improve battery life, page turn speed, and general performance
on underpowered ereaders with small memory and storage capacities. on underpowered ereaders with small storage capacities.
KCC avoids many common formatting issues (some of which occur [even on the Kindle Store](https://github.com/ciromattia/kcc/wiki/Kindle-Store-bad-formatting)), such as: KCC avoids many common formatting issues (some of which occur [even on the Kindle Store](https://github.com/ciromattia/kcc/wiki/Kindle-Store-bad-formatting)), such as:
1) faded black levels causing unneccessarily low contrast, which is hard to see and can cause eyestrain. 1) faded black levels causing unneccessarily low contrast, which is hard to see and can cause eyestrain.
@@ -92,7 +92,7 @@ Click on **Assets** of the latest release.
You probably want either You probably want either
- `KCC_*.*.*.exe` (Windows) - `KCC_*.*.*.exe` (Windows)
- `kcc_macos_arm_*.*.*.dmg` (recent Mac with Apple Silicon M1 chip or later) - `kcc_macos_arm_*.*.*.dmg` (recent Mac with Apple Silicon M1 chip or later)
- `kcc_macos_i386_*.*.*.dmg` (older Mac with Intel chip macOS 14+) - `kcc_macos_i386_*.*.*.dmg` (older Mac with Intel chip macOS 12+)
There are also legacy macOS 10.14+ and Windows 7 experimental versions available. There are also legacy macOS 10.14+ and Windows 7 experimental versions available.
@@ -104,7 +104,7 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
## FAQ ## FAQ
- Should I use Calibre? - Should I use Calibre?
- No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output (even just metadata!) in Calibre can break the formatting. - No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output (even just metadata!) in Calibre will break the formatting.
Viewing KCC output in Calibre will also not work properly. Viewing KCC output in Calibre will also not work properly.
On 7th gen and later Kindles running firmware 5.15.1+, you can get cover thumbnails simply by USB dropping into documents folder. On 7th gen and later Kindles running firmware 5.15.1+, you can get cover thumbnails simply by USB dropping into documents folder.
On 6th gen and older, you can get cover thumbnails by keeping Kindle plugged in during conversion. On 6th gen and older, you can get cover thumbnails by keeping Kindle plugged in during conversion.
@@ -318,7 +318,6 @@ Depending on your system [Python](https://www.python.org) may be called either `
If you want to edit the code, a good code editor is [VS Code](https://code.visualstudio.com). If you want to edit the code, a good code editor is [VS Code](https://code.visualstudio.com).
If you want to edit the `.ui` files, use `pyside6-designer` which is included in the `pip install pyside6`. If you want to edit the `.ui` files, use `pyside6-designer` which is included in the `pip install pyside6`.
If new objects have been added, verify that correct tab order has been applied by using [Tab Order Editing Mode](https://doc.qt.io/qt-6/designer-tab-order.html).
Then use the `gen_ui_files` scripts to autogenerate the python UI. Then use the `gen_ui_files` scripts to autogenerate the python UI.
An example PR adding a new checkbox is here: https://github.com/ciromattia/kcc/pull/785 An example PR adding a new checkbox is here: https://github.com/ciromattia/kcc/pull/785

16
environment.yml Normal file
View File

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

View File

@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>566</width> <width>566</width>
<height>581</height> <height>573</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -65,7 +65,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Enable right-to-left reading.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Enable right-to-left reading.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Right-to-left (manga)</string> <string>Right-to-left mode</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -348,51 +348,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2">
<widget class="QWidget" name="outputFolderWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="defaultOutputFolderBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - next to source&lt;br/&gt;&lt;/span&gt;Place output files next to source files&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - folder next to source&lt;br/&gt;&lt;/span&gt;Place output files in a folder next to source files&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - Custom&lt;br/&gt;&lt;/span&gt;Place output files in custom directory specified by right button&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Output Folder</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="defaultOutputFolderButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use this to select the default output directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/folder_new.png</normaloff>:/Other/icons/folder_new.png</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@@ -792,7 +747,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add CBR, CBZ, CB7 or PDF file to queue.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add CBR, CBZ, CB7 or PDF file to queue.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Add input file(s)</string> <string>Add file(s)</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="KCC.qrc"> <iconset resource="KCC.qrc">
@@ -800,19 +755,19 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="4"> <item row="0" column="5">
<widget class="QPushButton" name="directoryButton"> <widget class="QPushButton" name="defaultOutputFolderButton">
<property name="sizePolicy"> <property name="minimumSize">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum"> <size>
<horstretch>0</horstretch> <width>0</width>
<verstretch>0</verstretch> <height>30</height>
</sizepolicy> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add directory containing JPG, PNG or GIF files to queue.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;CBR, CBZ and CB7 files inside will not be processed!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use this to select the default output directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Add input folder(s)</string> <string/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="KCC.qrc"> <iconset resource="KCC.qrc">
@@ -820,7 +775,26 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="4"> <item row="0" column="4">
<widget class="QCheckBox" name="defaultOutputFolderBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - next to source&lt;br/&gt;&lt;/span&gt;Place output files next to source files&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - folder next to source&lt;br/&gt;&lt;/span&gt;Place output files in a folder next to source files&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - Custom&lt;br/&gt;&lt;/span&gt;Place output files in custom directory specified by right button&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Output Folder</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="4" colspan="2">
<widget class="QComboBox" name="formatBox"> <widget class="QComboBox" name="formatBox">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
@@ -837,9 +811,10 @@
<zorder>clearButton</zorder> <zorder>clearButton</zorder>
<zorder>deviceBox</zorder> <zorder>deviceBox</zorder>
<zorder>convertButton</zorder> <zorder>convertButton</zorder>
<zorder>fileButton</zorder>
<zorder>directoryButton</zorder>
<zorder>formatBox</zorder> <zorder>formatBox</zorder>
<zorder>defaultOutputFolderButton</zorder>
<zorder>fileButton</zorder>
<zorder>defaultOutputFolderBox</zorder>
</widget> </widget>
</item> </item>
<item row="6" column="0"> <item row="6" column="0">
@@ -923,44 +898,36 @@
</widget> </widget>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>jobList</tabstop> <tabstop>convertButton</tabstop>
<tabstop>fileButton</tabstop>
<tabstop>clearButton</tabstop> <tabstop>clearButton</tabstop>
<tabstop>deviceBox</tabstop> <tabstop>deviceBox</tabstop>
<tabstop>widthBox</tabstop> <tabstop>formatBox</tabstop>
<tabstop>heightBox</tabstop>
<tabstop>convertButton</tabstop>
<tabstop>mangaBox</tabstop> <tabstop>mangaBox</tabstop>
<tabstop>rotateBox</tabstop> <tabstop>rotateBox</tabstop>
<tabstop>qualityBox</tabstop> <tabstop>qualityBox</tabstop>
<tabstop>webtoonBox</tabstop> <tabstop>webtoonBox</tabstop>
<tabstop>upscaleBox</tabstop> <tabstop>upscaleBox</tabstop>
<tabstop>gammaBox</tabstop> <tabstop>gammaBox</tabstop>
<tabstop>gammaSlider</tabstop>
<tabstop>borderBox</tabstop> <tabstop>borderBox</tabstop>
<tabstop>outputSplit</tabstop> <tabstop>outputSplit</tabstop>
<tabstop>colorBox</tabstop> <tabstop>colorBox</tabstop>
<tabstop>mozJpegBox</tabstop> <tabstop>mozJpegBox</tabstop>
<tabstop>maximizeStrips</tabstop> <tabstop>maximizeStrips</tabstop>
<tabstop>croppingBox</tabstop> <tabstop>croppingBox</tabstop>
<tabstop>croppingPowerSlider</tabstop>
<tabstop>preserveMarginBox</tabstop>
<tabstop>spreadShiftBox</tabstop> <tabstop>spreadShiftBox</tabstop>
<tabstop>deleteBox</tabstop> <tabstop>deleteBox</tabstop>
<tabstop>disableProcessingBox</tabstop> <tabstop>disableProcessingBox</tabstop>
<tabstop>fileFusionBox</tabstop> <tabstop>chunkSizeBox</tabstop>
<tabstop>noRotateBox</tabstop> <tabstop>noRotateBox</tabstop>
<tabstop>interPanelCropBox</tabstop> <tabstop>interPanelCropBox</tabstop>
<tabstop>metadataTitleBox</tabstop>
<tabstop>chunkSizeCheckBox</tabstop>
<tabstop>chunkSizeBox</tabstop>
<tabstop>eraseRainbowBox</tabstop> <tabstop>eraseRainbowBox</tabstop>
<tabstop>rotateFirstBox</tabstop> <tabstop>heightBox</tabstop>
<tabstop>autoLevelBox</tabstop> <tabstop>croppingPowerSlider</tabstop>
<tabstop>autocontrastBox</tabstop>
<tabstop>editorButton</tabstop> <tabstop>editorButton</tabstop>
<tabstop>kofiButton</tabstop>
<tabstop>wikiButton</tabstop> <tabstop>wikiButton</tabstop>
<tabstop>jobList</tabstop>
<tabstop>gammaSlider</tabstop>
<tabstop>widthBox</tabstop>
</tabstops> </tabstops>
<resources> <resources>
<include location="KCC.qrc"/> <include location="KCC.qrc"/>

View File

@@ -192,18 +192,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<tabstops>
<tabstop>seriesLine</tabstop>
<tabstop>volumeLine</tabstop>
<tabstop>titleLine</tabstop>
<tabstop>numberLine</tabstop>
<tabstop>writerLine</tabstop>
<tabstop>pencillerLine</tabstop>
<tabstop>inkerLine</tabstop>
<tabstop>coloristLine</tabstop>
<tabstop>okButton</tabstop>
<tabstop>cancelButton</tabstop>
</tabstops>
<resources> <resources>
<include location="KCC.qrc"/> <include location="KCC.qrc"/>
</resources> </resources>

View File

@@ -22,7 +22,7 @@ import itertools
from pathlib import Path from pathlib import Path
from PySide6.QtCore import (QSize, QUrl, Qt, Signal, QIODeviceBase, QEvent, QThread, QSettings) from PySide6.QtCore import (QSize, QUrl, Qt, Signal, QIODeviceBase, QEvent, QThread, QSettings)
from PySide6.QtGui import (QColor, QIcon, QPixmap, QDesktopServices) from PySide6.QtGui import (QColor, QIcon, QPixmap, QDesktopServices)
from PySide6.QtWidgets import (QApplication, QLabel, QListWidgetItem, QMainWindow, QSystemTrayIcon, QFileDialog, QMessageBox, QDialog, QTreeView, QAbstractItemView) from PySide6.QtWidgets import (QApplication, QLabel, QListWidgetItem, QMainWindow, QSystemTrayIcon, QFileDialog, QMessageBox, QDialog)
from PySide6.QtNetwork import (QLocalSocket, QLocalServer) from PySide6.QtNetwork import (QLocalSocket, QLocalServer)
import os import os
@@ -382,14 +382,13 @@ class WorkerThread(QThread):
error_message = 'Process Failed. Custom title can\'t be set when processing more than 1 source.\nDid you forget to check fusion?' error_message = 'Process Failed. Custom title can\'t be set when processing more than 1 source.\nDid you forget to check fusion?'
print(error_message) print(error_message)
MW.addMessage.emit(error_message, 'error', True) MW.addMessage.emit(error_message, 'error', True)
for i, job in enumerate(currentJobs, start=1): for job in currentJobs:
job_progress_number = f'[{i}/{len(currentJobs)}] '
sleep(0.5) sleep(0.5)
if not self.conversionAlive: if not self.conversionAlive:
self.clean() self.clean()
return return
self.errors = False self.errors = False
MW.addMessage.emit(f'<b>{job_progress_number}Source:</b> ' + job, 'info', False) MW.addMessage.emit('<b>Source:</b> ' + job, 'info', False)
if gui_current_format == 'CBZ': if gui_current_format == 'CBZ':
MW.addMessage.emit('Creating CBZ files', 'info', False) MW.addMessage.emit('Creating CBZ files', 'info', False)
GUI.progress.content = 'Creating CBZ files' GUI.progress.content = 'Creating CBZ files'
@@ -403,7 +402,7 @@ class WorkerThread(QThread):
jobargv.append(job) jobargv.append(job)
try: try:
comic2ebook.options = comic2ebook.checkOptions(copy(options)) comic2ebook.options = comic2ebook.checkOptions(copy(options))
outputPath = comic2ebook.makeBook(job, self, job_progress_number) outputPath = comic2ebook.makeBook(job, self)
MW.hideProgressBar.emit() MW.hideProgressBar.emit()
except UserWarning as warn: except UserWarning as warn:
if not self.conversionAlive: if not self.conversionAlive:
@@ -445,7 +444,7 @@ class WorkerThread(QThread):
else: else:
MW.addMessage.emit('Creating EPUB files... <b>Done!</b>', 'info', True) MW.addMessage.emit('Creating EPUB files... <b>Done!</b>', 'info', True)
if 'MOBI' in gui_current_format: if 'MOBI' in gui_current_format:
MW.progressBarTick.emit(f'{job_progress_number}Creating MOBI files') MW.progressBarTick.emit('Creating MOBI files')
MW.progressBarTick.emit(str(len(outputPath) * 2 + 1)) MW.progressBarTick.emit(str(len(outputPath) * 2 + 1))
MW.progressBarTick.emit('tick') MW.progressBarTick.emit('tick')
MW.addMessage.emit('Creating MOBI files', 'info', False) MW.addMessage.emit('Creating MOBI files', 'info', False)
@@ -610,30 +609,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'Comic (*.pdf);;All (*.*)') 'Comic (*.pdf);;All (*.*)')
for fname in fnames[0]: for fname in fnames[0]:
if fname != '': if fname != '':
if sys.platform.startswith('win'):
fname = fname.replace('/', '\\')
self.lastPath = os.path.abspath(os.path.join(fname, os.pardir)) self.lastPath = os.path.abspath(os.path.join(fname, os.pardir))
GUI.jobList.addItem(fname) GUI.jobList.addItem(fname)
GUI.jobList.scrollToBottom() GUI.jobList.scrollToBottom()
def selectDir(self):
if self.needClean:
self.needClean = False
GUI.jobList.clear()
dialog = QFileDialog(MW, 'Select input folder(s)', self.lastPath)
dialog.setFileMode(QFileDialog.FileMode.Directory)
dialog.setOption(QFileDialog.Option.ShowDirsOnly, True)
dialog.setOption(QFileDialog.Option.DontUseNativeDialog, True)
dialog.findChild(QTreeView).setSelectionMode(QAbstractItemView.ExtendedSelection)
if dialog.exec():
dnames = dialog.selectedFiles()
for dname in dnames:
if dname != '':
self.lastPath = os.path.abspath(os.path.join(dname, os.pardir))
GUI.jobList.addItem(dname)
GUI.jobList.scrollToBottom()
def selectFileMetaEditor(self, sname): def selectFileMetaEditor(self, sname):
if not sname: if not sname:
if QApplication.keyboardModifiers() == Qt.ShiftModifier: if QApplication.keyboardModifiers() == Qt.ShiftModifier:
@@ -1046,7 +1027,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
MW.activateWindow() MW.activateWindow()
if type(message) is bytes: if type(message) is bytes:
message = message.decode('UTF-8') message = message.decode('UTF-8')
if not self.conversionAlive and message != 'ARISE' and not GUI.jobList.findItems(message, Qt.MatchFlag.MatchExactly): if not self.conversionAlive and message != 'ARISE':
if self.needClean: if self.needClean:
self.needClean = False self.needClean = False
GUI.jobList.clear() GUI.jobList.clear()
@@ -1077,7 +1058,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if message[-1] == '/': if message[-1] == '/':
message = message[:-1] message = message[:-1]
self.handleMessage(message) self.handleMessage(message)
GUI.jobList.sortItems()
def forceShutdown(self): def forceShutdown(self):
self.saveSettings(None) self.saveSettings(None)
@@ -1337,7 +1317,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.defaultOutputFolderButton.clicked.connect(self.selectDefaultOutputFolder) GUI.defaultOutputFolderButton.clicked.connect(self.selectDefaultOutputFolder)
GUI.clearButton.clicked.connect(self.clearJobs) GUI.clearButton.clicked.connect(self.clearJobs)
GUI.fileButton.clicked.connect(self.selectFile) GUI.fileButton.clicked.connect(self.selectFile)
GUI.directoryButton.clicked.connect(self.selectDir)
GUI.editorButton.clicked.connect(self.selectFileMetaEditor) GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
GUI.wikiButton.clicked.connect(self.openWiki) GUI.wikiButton.clicked.connect(self.openWiki)
GUI.kofiButton.clicked.connect(self.openKofi) GUI.kofiButton.clicked.connect(self.openKofi)

View File

@@ -26,7 +26,7 @@ class Ui_mainWindow(object):
def setupUi(self, mainWindow): def setupUi(self, mainWindow):
if not mainWindow.objectName(): if not mainWindow.objectName():
mainWindow.setObjectName(u"mainWindow") mainWindow.setObjectName(u"mainWindow")
mainWindow.resize(566, 581) mainWindow.resize(566, 573)
icon = QIcon() icon = QIcon()
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
mainWindow.setWindowIcon(icon) mainWindow.setWindowIcon(icon)
@@ -190,33 +190,6 @@ class Ui_mainWindow(object):
self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1) self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1)
self.outputFolderWidget = QWidget(self.optionWidget)
self.outputFolderWidget.setObjectName(u"outputFolderWidget")
self.horizontalLayout_3 = QHBoxLayout(self.outputFolderWidget)
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
self.defaultOutputFolderBox = QCheckBox(self.outputFolderWidget)
self.defaultOutputFolderBox.setObjectName(u"defaultOutputFolderBox")
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth())
self.defaultOutputFolderBox.setSizePolicy(sizePolicy1)
self.defaultOutputFolderBox.setTristate(True)
self.horizontalLayout_3.addWidget(self.defaultOutputFolderBox)
self.defaultOutputFolderButton = QPushButton(self.outputFolderWidget)
self.defaultOutputFolderButton.setObjectName(u"defaultOutputFolderButton")
self.defaultOutputFolderButton.setMinimumSize(QSize(0, 30))
icon1 = QIcon()
icon1.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.defaultOutputFolderButton.setIcon(icon1)
self.horizontalLayout_3.addWidget(self.defaultOutputFolderButton)
self.gridLayout_2.addWidget(self.outputFolderWidget, 0, 2, 1, 1)
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
@@ -238,11 +211,11 @@ class Ui_mainWindow(object):
self.gridLayout_3.setContentsMargins(0, 0, 0, 0) self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
self.hLabel = QLabel(self.customWidget) self.hLabel = QLabel(self.customWidget)
self.hLabel.setObjectName(u"hLabel") self.hLabel.setObjectName(u"hLabel")
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred) sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
sizePolicy2.setHorizontalStretch(0) sizePolicy1.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0) sizePolicy1.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth()) sizePolicy1.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
self.hLabel.setSizePolicy(sizePolicy2) self.hLabel.setSizePolicy(sizePolicy1)
self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1) self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
@@ -254,8 +227,8 @@ class Ui_mainWindow(object):
self.wLabel = QLabel(self.customWidget) self.wLabel = QLabel(self.customWidget)
self.wLabel.setObjectName(u"wLabel") self.wLabel.setObjectName(u"wLabel")
sizePolicy2.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth()) sizePolicy1.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
self.wLabel.setSizePolicy(sizePolicy2) self.wLabel.setSizePolicy(sizePolicy1)
self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1) self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
@@ -298,18 +271,18 @@ class Ui_mainWindow(object):
self.editorButton = QPushButton(self.toolWidget) self.editorButton = QPushButton(self.toolWidget)
self.editorButton.setObjectName(u"editorButton") self.editorButton.setObjectName(u"editorButton")
self.editorButton.setMinimumSize(QSize(0, 30)) self.editorButton.setMinimumSize(QSize(0, 30))
icon2 = QIcon() icon1 = QIcon()
icon2.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon1.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.editorButton.setIcon(icon2) self.editorButton.setIcon(icon1)
self.horizontalLayout.addWidget(self.editorButton) self.horizontalLayout.addWidget(self.editorButton)
self.kofiButton = QPushButton(self.toolWidget) self.kofiButton = QPushButton(self.toolWidget)
self.kofiButton.setObjectName(u"kofiButton") self.kofiButton.setObjectName(u"kofiButton")
self.kofiButton.setMinimumSize(QSize(0, 30)) self.kofiButton.setMinimumSize(QSize(0, 30))
icon3 = QIcon() icon2 = QIcon()
icon3.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon2.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.kofiButton.setIcon(icon3) self.kofiButton.setIcon(icon2)
self.kofiButton.setIconSize(QSize(19, 16)) self.kofiButton.setIconSize(QSize(19, 16))
self.horizontalLayout.addWidget(self.kofiButton) self.horizontalLayout.addWidget(self.kofiButton)
@@ -317,9 +290,9 @@ class Ui_mainWindow(object):
self.wikiButton = QPushButton(self.toolWidget) self.wikiButton = QPushButton(self.toolWidget)
self.wikiButton.setObjectName(u"wikiButton") self.wikiButton.setObjectName(u"wikiButton")
self.wikiButton.setMinimumSize(QSize(0, 30)) self.wikiButton.setMinimumSize(QSize(0, 30))
icon4 = QIcon() icon3 = QIcon()
icon4.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon3.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.wikiButton.setIcon(icon4) self.wikiButton.setIcon(icon3)
self.horizontalLayout.addWidget(self.wikiButton) self.horizontalLayout.addWidget(self.wikiButton)
@@ -363,8 +336,11 @@ class Ui_mainWindow(object):
self.preserveMarginBox = QSpinBox(self.croppingWidget) self.preserveMarginBox = QSpinBox(self.croppingWidget)
self.preserveMarginBox.setObjectName(u"preserveMarginBox") self.preserveMarginBox.setObjectName(u"preserveMarginBox")
sizePolicy1.setHeightForWidth(self.preserveMarginBox.sizePolicy().hasHeightForWidth()) sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.preserveMarginBox.setSizePolicy(sizePolicy1) sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.preserveMarginBox.sizePolicy().hasHeightForWidth())
self.preserveMarginBox.setSizePolicy(sizePolicy2)
self.preserveMarginBox.setMaximum(99) self.preserveMarginBox.setMaximum(99)
self.preserveMarginBox.setSingleStep(5) self.preserveMarginBox.setSingleStep(5)
self.preserveMarginBox.setValue(0) self.preserveMarginBox.setValue(0)
@@ -388,18 +364,18 @@ class Ui_mainWindow(object):
self.convertButton.setObjectName(u"convertButton") self.convertButton.setObjectName(u"convertButton")
self.convertButton.setMinimumSize(QSize(0, 30)) self.convertButton.setMinimumSize(QSize(0, 30))
self.convertButton.setFont(font) self.convertButton.setFont(font)
icon5 = QIcon() icon4 = QIcon()
icon5.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon4.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.convertButton.setIcon(icon5) self.convertButton.setIcon(icon4)
self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1) self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1)
self.clearButton = QPushButton(self.buttonWidget) self.clearButton = QPushButton(self.buttonWidget)
self.clearButton.setObjectName(u"clearButton") self.clearButton.setObjectName(u"clearButton")
self.clearButton.setMinimumSize(QSize(0, 30)) self.clearButton.setMinimumSize(QSize(0, 30))
icon6 = QIcon() icon5 = QIcon()
icon6.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon5.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.clearButton.setIcon(icon6) self.clearButton.setIcon(icon5)
self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1) self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1)
@@ -412,35 +388,42 @@ class Ui_mainWindow(object):
self.fileButton = QPushButton(self.buttonWidget) self.fileButton = QPushButton(self.buttonWidget)
self.fileButton.setObjectName(u"fileButton") self.fileButton.setObjectName(u"fileButton")
self.fileButton.setMinimumSize(QSize(0, 30)) self.fileButton.setMinimumSize(QSize(0, 30))
icon7 = QIcon() icon6 = QIcon()
icon7.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon6.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.fileButton.setIcon(icon7) self.fileButton.setIcon(icon6)
self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1) self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1)
self.directoryButton = QPushButton(self.buttonWidget) self.defaultOutputFolderButton = QPushButton(self.buttonWidget)
self.directoryButton.setObjectName(u"directoryButton") self.defaultOutputFolderButton.setObjectName(u"defaultOutputFolderButton")
sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum) self.defaultOutputFolderButton.setMinimumSize(QSize(0, 30))
sizePolicy4.setHorizontalStretch(0) icon7 = QIcon()
sizePolicy4.setVerticalStretch(0) icon7.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
sizePolicy4.setHeightForWidth(self.directoryButton.sizePolicy().hasHeightForWidth()) self.defaultOutputFolderButton.setIcon(icon7)
self.directoryButton.setSizePolicy(sizePolicy4)
self.directoryButton.setIcon(icon1)
self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1) self.gridLayout_4.addWidget(self.defaultOutputFolderButton, 0, 5, 1, 1)
self.defaultOutputFolderBox = QCheckBox(self.buttonWidget)
self.defaultOutputFolderBox.setObjectName(u"defaultOutputFolderBox")
sizePolicy2.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth())
self.defaultOutputFolderBox.setSizePolicy(sizePolicy2)
self.defaultOutputFolderBox.setTristate(True)
self.gridLayout_4.addWidget(self.defaultOutputFolderBox, 0, 4, 1, 1)
self.formatBox = QComboBox(self.buttonWidget) self.formatBox = QComboBox(self.buttonWidget)
self.formatBox.setObjectName(u"formatBox") self.formatBox.setObjectName(u"formatBox")
self.formatBox.setMinimumSize(QSize(0, 28)) self.formatBox.setMinimumSize(QSize(0, 28))
self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 1) self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 2)
self.clearButton.raise_() self.clearButton.raise_()
self.deviceBox.raise_() self.deviceBox.raise_()
self.convertButton.raise_() self.convertButton.raise_()
self.fileButton.raise_()
self.directoryButton.raise_()
self.formatBox.raise_() self.formatBox.raise_()
self.defaultOutputFolderButton.raise_()
self.fileButton.raise_()
self.defaultOutputFolderBox.raise_()
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2) self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
@@ -455,11 +438,11 @@ class Ui_mainWindow(object):
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
self.chunkSizeLabel = QLabel(self.chunkSizeWidget) self.chunkSizeLabel = QLabel(self.chunkSizeWidget)
self.chunkSizeLabel.setObjectName(u"chunkSizeLabel") self.chunkSizeLabel.setObjectName(u"chunkSizeLabel")
sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred) sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
sizePolicy5.setHorizontalStretch(0) sizePolicy4.setHorizontalStretch(0)
sizePolicy5.setVerticalStretch(0) sizePolicy4.setVerticalStretch(0)
sizePolicy5.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth()) sizePolicy4.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth())
self.chunkSizeLabel.setSizePolicy(sizePolicy5) self.chunkSizeLabel.setSizePolicy(sizePolicy4)
self.horizontalLayout_4.addWidget(self.chunkSizeLabel) self.horizontalLayout_4.addWidget(self.chunkSizeLabel)
@@ -473,8 +456,8 @@ class Ui_mainWindow(object):
self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget) self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget)
self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel") self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel")
sizePolicy5.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth()) sizePolicy4.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth())
self.chunkSizeWarnLabel.setSizePolicy(sizePolicy5) self.chunkSizeWarnLabel.setSizePolicy(sizePolicy4)
self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel) self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel)
@@ -486,43 +469,35 @@ class Ui_mainWindow(object):
self.statusBar.setObjectName(u"statusBar") self.statusBar.setObjectName(u"statusBar")
self.statusBar.setSizeGripEnabled(False) self.statusBar.setSizeGripEnabled(False)
mainWindow.setStatusBar(self.statusBar) mainWindow.setStatusBar(self.statusBar)
QWidget.setTabOrder(self.jobList, self.fileButton) QWidget.setTabOrder(self.convertButton, self.clearButton)
QWidget.setTabOrder(self.fileButton, self.clearButton)
QWidget.setTabOrder(self.clearButton, self.deviceBox) QWidget.setTabOrder(self.clearButton, self.deviceBox)
QWidget.setTabOrder(self.deviceBox, self.widthBox) QWidget.setTabOrder(self.deviceBox, self.formatBox)
QWidget.setTabOrder(self.widthBox, self.heightBox) QWidget.setTabOrder(self.formatBox, self.mangaBox)
QWidget.setTabOrder(self.heightBox, self.convertButton)
QWidget.setTabOrder(self.convertButton, self.mangaBox)
QWidget.setTabOrder(self.mangaBox, self.rotateBox) QWidget.setTabOrder(self.mangaBox, self.rotateBox)
QWidget.setTabOrder(self.rotateBox, self.qualityBox) QWidget.setTabOrder(self.rotateBox, self.qualityBox)
QWidget.setTabOrder(self.qualityBox, self.webtoonBox) QWidget.setTabOrder(self.qualityBox, self.webtoonBox)
QWidget.setTabOrder(self.webtoonBox, self.upscaleBox) QWidget.setTabOrder(self.webtoonBox, self.upscaleBox)
QWidget.setTabOrder(self.upscaleBox, self.gammaBox) QWidget.setTabOrder(self.upscaleBox, self.gammaBox)
QWidget.setTabOrder(self.gammaBox, self.gammaSlider) QWidget.setTabOrder(self.gammaBox, self.borderBox)
QWidget.setTabOrder(self.gammaSlider, self.borderBox)
QWidget.setTabOrder(self.borderBox, self.outputSplit) QWidget.setTabOrder(self.borderBox, self.outputSplit)
QWidget.setTabOrder(self.outputSplit, self.colorBox) QWidget.setTabOrder(self.outputSplit, self.colorBox)
QWidget.setTabOrder(self.colorBox, self.mozJpegBox) QWidget.setTabOrder(self.colorBox, self.mozJpegBox)
QWidget.setTabOrder(self.mozJpegBox, self.maximizeStrips) QWidget.setTabOrder(self.mozJpegBox, self.maximizeStrips)
QWidget.setTabOrder(self.maximizeStrips, self.croppingBox) QWidget.setTabOrder(self.maximizeStrips, self.croppingBox)
QWidget.setTabOrder(self.croppingBox, self.croppingPowerSlider) QWidget.setTabOrder(self.croppingBox, self.spreadShiftBox)
QWidget.setTabOrder(self.croppingPowerSlider, self.preserveMarginBox)
QWidget.setTabOrder(self.preserveMarginBox, self.spreadShiftBox)
QWidget.setTabOrder(self.spreadShiftBox, self.deleteBox) QWidget.setTabOrder(self.spreadShiftBox, self.deleteBox)
QWidget.setTabOrder(self.deleteBox, self.disableProcessingBox) QWidget.setTabOrder(self.deleteBox, self.disableProcessingBox)
QWidget.setTabOrder(self.disableProcessingBox, self.fileFusionBox) QWidget.setTabOrder(self.disableProcessingBox, self.chunkSizeBox)
QWidget.setTabOrder(self.fileFusionBox, self.noRotateBox) QWidget.setTabOrder(self.chunkSizeBox, self.noRotateBox)
QWidget.setTabOrder(self.noRotateBox, self.interPanelCropBox) QWidget.setTabOrder(self.noRotateBox, self.interPanelCropBox)
QWidget.setTabOrder(self.interPanelCropBox, self.metadataTitleBox) QWidget.setTabOrder(self.interPanelCropBox, self.eraseRainbowBox)
QWidget.setTabOrder(self.metadataTitleBox, self.chunkSizeCheckBox) QWidget.setTabOrder(self.eraseRainbowBox, self.heightBox)
QWidget.setTabOrder(self.chunkSizeCheckBox, self.chunkSizeBox) QWidget.setTabOrder(self.heightBox, self.croppingPowerSlider)
QWidget.setTabOrder(self.chunkSizeBox, self.eraseRainbowBox) QWidget.setTabOrder(self.croppingPowerSlider, self.editorButton)
QWidget.setTabOrder(self.eraseRainbowBox, self.rotateFirstBox) QWidget.setTabOrder(self.editorButton, self.wikiButton)
QWidget.setTabOrder(self.rotateFirstBox, self.autoLevelBox) QWidget.setTabOrder(self.wikiButton, self.jobList)
QWidget.setTabOrder(self.autoLevelBox, self.autocontrastBox) QWidget.setTabOrder(self.jobList, self.gammaSlider)
QWidget.setTabOrder(self.autocontrastBox, self.editorButton) QWidget.setTabOrder(self.gammaSlider, self.widthBox)
QWidget.setTabOrder(self.editorButton, self.kofiButton)
QWidget.setTabOrder(self.kofiButton, self.wikiButton)
self.retranslateUi(mainWindow) self.retranslateUi(mainWindow)
@@ -538,7 +513,7 @@ class Ui_mainWindow(object):
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>", None)) self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left (manga)", None)) self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left mode", None))
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>", None)) self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
@@ -635,14 +610,6 @@ class Ui_mainWindow(object):
self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - BW only<br/></span>Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Disabled<br/></span>Disable autocontrast</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - BW and Color<br/></span>BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.</p></body></html>", None)) self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - BW only<br/></span>Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Disabled<br/></span>Disable autocontrast</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - BW and Color<br/></span>BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None)) self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None))
#if QT_CONFIG(tooltip)
self.defaultOutputFolderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - next to source<br/></span>Place output files next to source files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - folder next to source<br/></span>Place output files in a folder next to source files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Custom<br/></span>Place output files in custom directory specified by right button</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.defaultOutputFolderBox.setText(QCoreApplication.translate("mainWindow", u"Output Folder", None))
#if QT_CONFIG(tooltip)
self.defaultOutputFolderButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Use this to select the default output directory.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.defaultOutputFolderButton.setText("")
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html>", None)) self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
@@ -683,11 +650,15 @@ class Ui_mainWindow(object):
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None)) self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add input file(s)", None)) self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add file(s)", None))
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>", None)) self.defaultOutputFolderButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Use this to select the default output directory.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add input folder(s)", None)) self.defaultOutputFolderButton.setText("")
#if QT_CONFIG(tooltip)
self.defaultOutputFolderBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - next to source<br/></span>Place output files next to source files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - folder next to source<br/></span>Place output files in a folder next to source files</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Custom<br/></span>Place output files in custom directory specified by right button</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.defaultOutputFolderBox.setText(QCoreApplication.translate("mainWindow", u"Output Folder", None))
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None)) self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)

View File

@@ -156,15 +156,6 @@ class Ui_editorDialog(object):
self.verticalLayout.addWidget(self.optionWidget) self.verticalLayout.addWidget(self.optionWidget)
QWidget.setTabOrder(self.seriesLine, self.volumeLine)
QWidget.setTabOrder(self.volumeLine, self.titleLine)
QWidget.setTabOrder(self.titleLine, self.numberLine)
QWidget.setTabOrder(self.numberLine, self.writerLine)
QWidget.setTabOrder(self.writerLine, self.pencillerLine)
QWidget.setTabOrder(self.pencillerLine, self.inkerLine)
QWidget.setTabOrder(self.inkerLine, self.coloristLine)
QWidget.setTabOrder(self.coloristLine, self.okButton)
QWidget.setTabOrder(self.okButton, self.cancelButton)
self.retranslateUi(editorDialog) self.retranslateUi(editorDialog)

View File

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

View File

@@ -43,7 +43,7 @@ from psutil import virtual_memory, disk_usage
from html import escape as hescape from html import escape as hescape
import pymupdf import pymupdf
from .shared import IMAGE_TYPES, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean from .shared import getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean
from .comicarchive import SEVENZIP, available_archive_tools from .comicarchive import SEVENZIP, available_archive_tools
from . import comic2panel from . import comic2panel
from . import image from . import image
@@ -456,7 +456,7 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
"</container>"]) "</container>"])
f.close() f.close()
def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, originalpath, job_progress='', len_tomes=0): def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, originalpath, len_tomes=0):
filelist = [] filelist = []
chapterlist = [] chapterlist = []
os.mkdir(os.path.join(path, 'OEBPS', 'Text')) os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
@@ -564,7 +564,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, ori
else: else:
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile))) filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
build_html_end = perf_counter() build_html_end = perf_counter()
print(f"{job_progress}buildHTML: {build_html_end - build_html_start} seconds") print(f"buildHTML: {build_html_end - build_html_start} seconds")
# Overwrite chapternames if ComicInfo.xml has bookmarks # Overwrite chapternames if ComicInfo.xml has bookmarks
if ischunked: if ischunked:
options.comicinfo_chapters = [] options.comicinfo_chapters = []
@@ -600,7 +600,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, ori
buildOPF(path, options.title, filelist, originalpath, cover) buildOPF(path, options.title, filelist, originalpath, cover)
def buildPDF(path, title, job_progress='', cover=None, output_file=None): def buildPDF(path, title, cover=None, output_file=None):
""" """
Build a PDF file from processed comic images. Build a PDF file from processed comic images.
Images are combined into a single PDF optimized for e-readers. Images are combined into a single PDF optimized for e-readers.
@@ -625,11 +625,11 @@ def buildPDF(path, title, job_progress='', cover=None, output_file=None):
# Save with optimizations for smaller file size # Save with optimizations for smaller file size
doc.save(output_file, deflate=True, garbage=4, clean=True) doc.save(output_file, deflate=True, garbage=4, clean=True)
end = perf_counter() end = perf_counter()
print(f"{job_progress}MuPDF output: {end-start} sec") print(f"MuPDF output: {end-start} sec")
return output_file return output_file
def imgDirectoryProcessing(path, job_progress=''): def imgDirectoryProcessing(path):
global workerPool, workerOutput global workerPool, workerOutput
workerPool = Pool(maxtasksperchild=100) workerPool = Pool(maxtasksperchild=100)
workerOutput = [] workerOutput = []
@@ -649,7 +649,7 @@ def imgDirectoryProcessing(path, job_progress=''):
workerPool.close() workerPool.close()
workerPool.join() workerPool.join()
img_processing_end = perf_counter() img_processing_end = perf_counter()
print(f"{job_progress}imgFileProcessing: {img_processing_end - img_processing_start} seconds") print(f"imgFileProcessing: {img_processing_end - img_processing_start} seconds")
# macOS 15 likes to add ._ files after multiprocessing # macOS 15 likes to add ._ files after multiprocessing
dot_clean(path) dot_clean(path)
@@ -851,16 +851,14 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height):
def getWorkFolder(afile, workdir=None): def getWorkFolder(afile):
if not workdir:
workdir = mkdtemp('', 'KCC-')
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
else:
fullPath = workdir
if os.path.isdir(afile): if os.path.isdir(afile):
if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5: if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
raise UserWarning("Not enough disk space to perform conversion.") raise UserWarning("Not enough disk space to perform conversion.")
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
try: try:
os.rmdir(workdir)
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
copytree(afile, fullPath) copytree(afile, fullPath)
sanitizePermissions(fullPath) sanitizePermissions(fullPath)
return workdir return workdir
@@ -871,8 +869,7 @@ def getWorkFolder(afile, workdir=None):
if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5: if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5:
raise UserWarning("Not enough disk space to perform conversion.") raise UserWarning("Not enough disk space to perform conversion.")
if afile.lower().endswith('.pdf'): if afile.lower().endswith('.pdf'):
if not os.path.exists(fullPath): workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
os.makedirs(fullPath)
path = workdir path = workdir
sanitizePermissions(path) sanitizePermissions(path)
target_height = options.profileData[1][1] target_height = options.profileData[1][1]
@@ -881,42 +878,39 @@ def getWorkFolder(afile, workdir=None):
elif options.cropping == 2: elif options.cropping == 2:
target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number
try: try:
mupdf_pdf_process_pages_parallel(afile, fullPath, target_height) mupdf_pdf_process_pages_parallel(afile, workdir, target_height)
except Exception as e: except Exception as e:
rmtree(path, True) rmtree(path, True)
raise UserWarning(f"Failed to extract images from PDF file. {e}") raise UserWarning(f"Failed to extract images from PDF file. {e}")
return workdir
else: else:
if not os.path.exists(fullPath): workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
os.makedirs(fullPath)
try: try:
cbx = comicarchive.ComicArchive(afile) cbx = comicarchive.ComicArchive(afile)
path = cbx.extract(fullPath) path = cbx.extract(workdir)
sanitizePermissions(path) sanitizePermissions(path)
tdir = os.listdir(fullPath) tdir = os.listdir(workdir)
if len(tdir) == 2 and 'ComicInfo.xml' in tdir: if len(tdir) == 2 and 'ComicInfo.xml' in tdir:
tdir.remove('ComicInfo.xml') tdir.remove('ComicInfo.xml')
if os.path.isdir(os.path.join(fullPath, tdir[0])): if os.path.isdir(os.path.join(workdir, tdir[0])):
os.replace( os.replace(
os.path.join(fullPath, 'ComicInfo.xml'), os.path.join(workdir, 'ComicInfo.xml'),
os.path.join(fullPath, tdir[0], 'ComicInfo.xml') os.path.join(workdir, tdir[0], 'ComicInfo.xml')
) )
if len(tdir) == 1 and os.path.isdir(os.path.join(fullPath, tdir[0])): if len(tdir) == 1 and os.path.isdir(os.path.join(workdir, tdir[0])):
for file in os.listdir(os.path.join(fullPath, tdir[0])): path = os.path.join(workdir, tdir[0])
move(os.path.join(fullPath, tdir[0], file), fullPath)
os.rmdir(os.path.join(fullPath, tdir[0]))
return workdir
except OSError as e: except OSError as e:
rmtree(workdir, True) rmtree(workdir, True)
raise UserWarning(e) raise UserWarning(e)
else: else:
raise UserWarning("Failed to open source file/directory.") raise UserWarning("Failed to open source file/directory.")
newpath = mkdtemp('', 'KCC-', os.path.dirname(afile))
os.renames(path, os.path.join(newpath, 'OEBPS', 'Images'))
return newpath
def getOutputFilename(srcpath, wantedname, ext, tomenumber): def getOutputFilename(srcpath, wantedname, ext, tomenumber):
source_path = Path(srcpath)
if srcpath[-1] == os.path.sep: if srcpath[-1] == os.path.sep:
srcpath = srcpath[:-1] srcpath = srcpath[:-1]
if 'Ko' in options.profile and options.format == 'EPUB': if 'Ko' in options.profile and options.format == 'EPUB':
@@ -926,29 +920,20 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
else: else:
ext = '.kepub.epub' ext = '.kepub.epub'
if wantedname is not None: if wantedname is not None:
wanted_root, wanted_ext = os.path.splitext(wantedname)
if wantedname.endswith(ext): if wantedname.endswith(ext):
filename = os.path.abspath(wantedname) filename = os.path.abspath(wantedname)
elif wanted_ext == '.mobi' and ext == '.epub': elif os.path.isdir(srcpath):
filename = os.path.abspath(wanted_root + ext) filename = os.path.join(os.path.abspath(options.output), os.path.basename(srcpath) + ext)
# output directory
else: else:
abs_path = os.path.abspath(options.output) filename = os.path.join(os.path.abspath(options.output),
if not os.path.exists(abs_path): os.path.basename(os.path.splitext(srcpath)[0]) + ext)
os.mkdir(abs_path)
if source_path.is_file():
filename = os.path.join(os.path.abspath(options.output), source_path.stem + tomenumber + ext)
else:
filename = os.path.join(os.path.abspath(options.output), source_path.name + tomenumber + ext)
elif os.path.isdir(srcpath): elif os.path.isdir(srcpath):
filename = srcpath + tomenumber + ext filename = srcpath + tomenumber + ext
else: else:
if 'Ko' in options.profile and options.format == 'EPUB': if 'Ko' in options.profile and options.format == 'EPUB':
if source_path.is_file(): src = pathlib.Path(srcpath)
name = re.sub(r'\W+', '_', source_path.stem) + tomenumber + ext name = re.sub(r'\W+', '_', src.stem) + tomenumber + ext
else: filename = src.with_name(name)
name = re.sub(r'\W+', '_', source_path.name) + tomenumber + ext
filename = source_path.with_name(name)
else: else:
filename = os.path.splitext(srcpath)[0] + tomenumber + ext filename = os.path.splitext(srcpath)[0] + tomenumber + ext
if os.path.isfile(filename): if os.path.isfile(filename):
@@ -1054,7 +1039,7 @@ def removeNonImages(filetree):
for root, dirs, files in os.walk(filetree): for root, dirs, files in os.walk(filetree):
for name in files: for name in files:
_, ext = getImageFileName(name) _, ext = getImageFileName(name)
if ext not in IMAGE_TYPES: if ext not in ('.png', '.jpg', '.jpeg', '.gif', '.webp', '.jp2', '.avif'):
if os.path.exists(os.path.join(root, name)): if os.path.exists(os.path.join(root, name)):
os.remove(os.path.join(root, name)) os.remove(os.path.join(root, name))
# remove empty nested folders # remove empty nested folders
@@ -1264,7 +1249,7 @@ def slugify(value, is_natural_sorted):
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2)) value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
return value return value
def makeZIP(zipfilename, basedir, job_progress='', isepub=False): def makeZIP(zipfilename, basedir, isepub=False):
start = perf_counter() start = perf_counter()
zipfilename = os.path.abspath(zipfilename) + '.zip' zipfilename = os.path.abspath(zipfilename) + '.zip'
if SEVENZIP in available_archive_tools(): if SEVENZIP in available_archive_tools():
@@ -1285,7 +1270,7 @@ def makeZIP(zipfilename, basedir, job_progress='', isepub=False):
zipOutput.write(path, aPath) zipOutput.write(path, aPath)
zipOutput.close() zipOutput.close()
end = perf_counter() end = perf_counter()
print(f"{job_progress}makeZIP time: {end - start} seconds") print(f"makeZIP time: {end - start} seconds")
return zipfilename return zipfilename
def makeParser(): def makeParser():
@@ -1530,15 +1515,17 @@ def makeFusion(sources: List[str]):
print(f"Processing {source}...") print(f"Processing {source}...")
checkPre(source) checkPre(source)
print("Checking images...") print("Checking images...")
path = getWorkFolder(source)
pathfinder = os.path.join(path, "OEBPS", "Images")
sanitizeTree(pathfinder)
# TODO: remove flattenTree when subchapters are supported
flattenTree(pathfinder)
source_path = Path(source) source_path = Path(source)
if source_path.is_file(): if source_path.is_file():
targetpath = fusion_path.joinpath(source_path.stem) os.renames(pathfinder, fusion_path.joinpath(source_path.stem))
else: else:
targetpath = fusion_path.joinpath(source_path.name) os.renames(pathfinder, fusion_path.joinpath(source_path.name))
getWorkFolder(source, str(targetpath))
sanitizeTree(targetpath)
# TODO: remove flattenTree when subchapters are supported
flattenTree(targetpath)
end = perf_counter() end = perf_counter()
print(f"makefusion: {end - start} seconds") print(f"makefusion: {end - start} seconds")
@@ -1547,7 +1534,7 @@ def makeFusion(sources: List[str]):
return str(fusion_path) return str(fusion_path)
def makeBook(source, qtgui=None, job_progress=''): def makeBook(source, qtgui=None):
start = perf_counter() start = perf_counter()
global GUI global GUI
GUI = qtgui GUI = qtgui
@@ -1555,12 +1542,11 @@ def makeBook(source, qtgui=None, job_progress=''):
GUI.progressBarTick.emit('1') GUI.progressBarTick.emit('1')
else: else:
checkTools(source) checkTools(source)
options.kindle_azw3 = options.iskindle and ('MOBI' in options.format or 'EPUB' in options.format)
options.kindle_scribe_azw3 = options.profile == 'KS' and ('MOBI' in options.format or 'EPUB' in options.format) options.kindle_scribe_azw3 = options.profile == 'KS' and ('MOBI' in options.format or 'EPUB' in options.format)
checkPre(source) checkPre(source)
print(f"{job_progress}Preparing source images...") print("Preparing source images...")
path = getWorkFolder(source) path = getWorkFolder(source)
print(f"{job_progress}Checking images...") print("Checking images...")
getMetadata(os.path.join(path, "OEBPS", "Images"), source) getMetadata(os.path.join(path, "OEBPS", "Images"), source)
removeNonImages(os.path.join(path, "OEBPS", "Images")) removeNonImages(os.path.join(path, "OEBPS", "Images"))
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source) detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
@@ -1571,14 +1557,14 @@ def makeBook(source, qtgui=None, job_progress=''):
if options.webtoon: if options.webtoon:
x, y = image.ProfileData.Profiles[options.profile][1] x, y = image.ProfileData.Profiles[options.profile][1]
comic2panel.main(['-y ' + str(y), '-x' + str(x), '-i', '-m', path], job_progress, qtgui) comic2panel.main(['-y ' + str(y), '-x' + str(x), '-i', '-m', path], qtgui)
if options.noprocessing: if options.noprocessing:
print(f"{job_progress}Do not process image, ignore any profile or processing option") print("Do not process image, ignore any profile or processing option")
else: else:
print(f"{job_progress}Processing images...") print("Processing images...")
if GUI: if GUI:
GUI.progressBarTick.emit(f'{job_progress}Processing images') GUI.progressBarTick.emit('Processing images')
imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"), job_progress) imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images"))
if GUI: if GUI:
GUI.progressBarTick.emit('1') GUI.progressBarTick.emit('1')
if options.batchsplit > 0 or options.targetsize: if options.batchsplit > 0 or options.targetsize:
@@ -1589,11 +1575,11 @@ def makeBook(source, qtgui=None, job_progress=''):
tomeNumber = 0 tomeNumber = 0
if GUI: if GUI:
if options.format == 'CBZ': if options.format == 'CBZ':
GUI.progressBarTick.emit(f'{job_progress}Compressing CBZ files') GUI.progressBarTick.emit('Compressing CBZ files')
elif options.format == 'PDF': elif options.format == 'PDF':
GUI.progressBarTick.emit(f'{job_progress}Creating PDF files') GUI.progressBarTick.emit('Creating PDF files')
else: else:
GUI.progressBarTick.emit(f'{job_progress}Compressing EPUB files') GUI.progressBarTick.emit('Compressing EPUB files')
GUI.progressBarTick.emit(str(len(tomes) + 1)) GUI.progressBarTick.emit(str(len(tomes) + 1))
GUI.progressBarTick.emit('tick') GUI.progressBarTick.emit('tick')
options.baseTitle = options.title options.baseTitle = options.title
@@ -1607,29 +1593,29 @@ def makeBook(source, qtgui=None, job_progress=''):
tomeNumber += 1 tomeNumber += 1
options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']' options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']'
if options.format == 'CBZ': if options.format == 'CBZ':
print(f"{job_progress}Creating CBZ file...") print("Creating CBZ file...")
if len(tomes) > 1: if len(tomes) > 1:
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber))) filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
else: else:
filepath.append(getOutputFilename(source, options.output, '.cbz', '')) filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress) makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"))
elif options.format == 'PDF': elif options.format == 'PDF':
print(f"{job_progress}Creating PDF file with PyMuPDF...") print("Creating PDF file with PyMuPDF...")
# determine output filename based on source and tome count # determine output filename based on source and tome count
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else '' suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
output_file = getOutputFilename(source, options.output, '.pdf', suffix) output_file = getOutputFilename(source, options.output, '.pdf', suffix)
# use optimized buildPDF logic with streaming and compression # use optimized buildPDF logic with streaming and compression
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file) output_pdf = buildPDF(tome, options.title, None, output_file)
filepath.append(output_pdf) filepath.append(output_pdf)
else: else:
print(f"{job_progress}Creating EPUB file...") print("Creating EPUB file...")
if len(tomes) > 1: if len(tomes) > 1:
buildEPUB(tome, chapterNames, tomeNumber, True, cover, source, job_progress, len(tomes)) buildEPUB(tome, chapterNames, tomeNumber, True, cover, source, len(tomes))
filepath.append(getOutputFilename(source, options.output, '.epub', ' ' + str(tomeNumber))) filepath.append(getOutputFilename(source, options.output, '.epub', ' ' + str(tomeNumber)))
else: else:
buildEPUB(tome, chapterNames, tomeNumber, False, cover, source, job_progress) buildEPUB(tome, chapterNames, tomeNumber, False, cover, source)
filepath.append(getOutputFilename(source, options.output, '.epub', '')) filepath.append(getOutputFilename(source, options.output, '.epub', ''))
makeZIP(tome + '_comic', tome, job_progress, True) makeZIP(tome + '_comic', tome, True)
# Copy files to final destination (PDF files are already saved directly) # Copy files to final destination (PDF files are already saved directly)
if options.format != 'PDF': if options.format != 'PDF':
copyfile(tome + '_comic.zip', filepath[-1]) copyfile(tome + '_comic.zip', filepath[-1])
@@ -1642,23 +1628,23 @@ def makeBook(source, qtgui=None, job_progress=''):
if GUI: if GUI:
GUI.progressBarTick.emit('tick') GUI.progressBarTick.emit('tick')
if not GUI and options.format == 'MOBI': if not GUI and options.format == 'MOBI':
print(f"{job_progress}Creating MOBI files...") print("Creating MOBI files...")
work = [] work = []
for i in filepath: for i in filepath:
work.append([i]) work.append([i])
output = makeMOBI(work, GUI) output = makeMOBI(work, GUI)
for errors in output: for errors in output:
if errors[0] != 0: if errors[0] != 0:
print(f"{job_progress}Error: KindleGen failed to create MOBI!") print('Error: KindleGen failed to create MOBI!')
print(errors) print(errors)
return filepath return filepath
k = kindle.Kindle(options.profile) k = kindle.Kindle(options.profile)
if k.path and k.coverSupport: if k.path and k.coverSupport:
print(f"{job_progress}Kindle detected. Uploading covers...") print("Kindle detected. Uploading covers...")
for i in filepath: for i in filepath:
output = makeMOBIFix(i, options.covers[filepath.index(i)][1]) output = makeMOBIFix(i, options.covers[filepath.index(i)][1])
if not output[0]: if not output[0]:
print(f'{job_progress}Error: Failed to tweak KindleGen output!') print('Error: Failed to tweak KindleGen output!')
return filepath return filepath
else: else:
os.remove(i.replace('.epub', '.mobi') + '_toclean') os.remove(i.replace('.epub', '.mobi') + '_toclean')
@@ -1671,7 +1657,7 @@ def makeBook(source, qtgui=None, job_progress=''):
rmtree(source, True) rmtree(source, True)
end = perf_counter() end = perf_counter()
print(f"{job_progress}makeBook: {end - start} seconds") print(f"makeBook: {end - start} seconds")
# Clean up temporary workspace # Clean up temporary workspace
try: try:
rmtree(path, True) rmtree(path, True)

View File

@@ -67,14 +67,13 @@ def mergeDirectory(work):
result = Image.new('RGB', (targetWidth, targetHeight)) result = Image.new('RGB', (targetWidth, targetHeight))
y = 0 y = 0
for i in imagesValid: for i in imagesValid:
with Image.open(i) as img: img = Image.open(i).convert('RGB')
img = img.convert('RGB') if img.size[0] < targetWidth or img.size[0] > targetWidth:
if img.size[0] < targetWidth or img.size[0] > targetWidth: widthPercent = (targetWidth / float(img.size[0]))
widthPercent = (targetWidth / float(img.size[0])) heightSize = int((float(img.size[1]) * float(widthPercent)))
heightSize = int((float(img.size[1]) * float(widthPercent))) img = ImageOps.fit(img, (targetWidth, heightSize), method=Image.BICUBIC, centering=(0.5, 0.5))
img = ImageOps.fit(img, (targetWidth, heightSize), method=Image.BICUBIC, centering=(0.5, 0.5)) result.paste(img, (0, y))
result.paste(img, (0, y)) y += img.size[1]
y += img.size[1]
os.remove(i) os.remove(i)
savePath = os.path.split(imagesValid[0]) savePath = os.path.split(imagesValid[0])
result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG') result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG')
@@ -222,7 +221,7 @@ def splitImage(work):
return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2]) return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
def main(argv=None, job_progress='', qtgui=None): def main(argv=None, qtgui=None):
global args, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput global args, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput
parser = ArgumentParser(prog="kcc-c2p", usage="kcc-c2p [options] [input]", add_help=False) parser = ArgumentParser(prog="kcc-c2p", usage="kcc-c2p [options] [input]", add_help=False)
@@ -254,14 +253,16 @@ def main(argv=None, job_progress='', qtgui=None):
return 1 return 1
if args.height > 0: if args.height > 0:
for sourceDir in args.input: for sourceDir in args.input:
targetDir = sourceDir targetDir = sourceDir + "-Splitted"
if os.path.isdir(sourceDir): if os.path.isdir(sourceDir):
rmtree(targetDir, True)
os.renames(sourceDir, targetDir)
work = [] work = []
pagenumber = 1 pagenumber = 1
splitWorkerOutput = [] splitWorkerOutput = []
splitWorkerPool = Pool(maxtasksperchild=10) splitWorkerPool = Pool(maxtasksperchild=10)
if args.merge: if args.merge:
print(f"{job_progress}Merging images...") print("Merging images...")
directoryNumer = 1 directoryNumer = 1
mergeWork = [] mergeWork = []
mergeWorkerOutput = [] mergeWorkerOutput = []
@@ -273,7 +274,7 @@ def main(argv=None, job_progress='', qtgui=None):
directoryNumer += 1 directoryNumer += 1
mergeWork.append([os.path.join(root, directory)]) mergeWork.append([os.path.join(root, directory)])
if GUI: if GUI:
GUI.progressBarTick.emit(f'{job_progress}Combining images') GUI.progressBarTick.emit('Combining images')
GUI.progressBarTick.emit(str(directoryNumer)) GUI.progressBarTick.emit(str(directoryNumer))
for i in mergeWork: for i in mergeWork:
mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick) mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick)
@@ -286,7 +287,7 @@ def main(argv=None, job_progress='', qtgui=None):
rmtree(targetDir, True) rmtree(targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0], raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
mergeWorkerOutput[0][1]) mergeWorkerOutput[0][1])
print(f"{job_progress}Splitting images...") print("Splitting images...")
dot_clean(targetDir) dot_clean(targetDir)
for root, _, files in os.walk(targetDir, False): for root, _, files in os.walk(targetDir, False):
for name in files: for name in files:
@@ -296,7 +297,7 @@ def main(argv=None, job_progress='', qtgui=None):
else: else:
os.remove(os.path.join(root, name)) os.remove(os.path.join(root, name))
if GUI: if GUI:
GUI.progressBarTick.emit(f'{job_progress}Splitting images') GUI.progressBarTick.emit('Splitting images')
GUI.progressBarTick.emit(str(pagenumber)) GUI.progressBarTick.emit(str(pagenumber))
GUI.progressBarTick.emit('tick') GUI.progressBarTick.emit('tick')
if len(work) > 0: if len(work) > 0:
@@ -312,6 +313,8 @@ def main(argv=None, job_progress='', qtgui=None):
rmtree(targetDir, True) rmtree(targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0], raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
splitWorkerOutput[0][1]) splitWorkerOutput[0][1])
if args.inPlace:
os.renames(targetDir, sourceDir)
else: else:
rmtree(targetDir, True) rmtree(targetDir, True)
raise UserWarning("C2P: Source directory is empty.") raise UserWarning("C2P: Source directory is empty.")

View File

@@ -20,13 +20,12 @@
from functools import cached_property, lru_cache from functools import cached_property, lru_cache
import os import os
from pathlib import Path
import platform import platform
import distro import distro
from subprocess import STDOUT, PIPE, CalledProcessError from subprocess import STDOUT, PIPE, CalledProcessError
from xml.dom.minidom import parseString from xml.dom.minidom import parseString
from xml.parsers.expat import ExpatError from xml.parsers.expat import ExpatError
from .shared import IMAGE_TYPES, subprocess_run from .shared import subprocess_run
EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.' EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.'
SEVENZIP = '7zz' if platform.system() == 'Darwin' else '7z' SEVENZIP = '7zz' if platform.system() == 'Darwin' else '7z'
@@ -67,9 +66,6 @@ class ComicArchive:
if not os.path.isdir(targetdir): if not os.path.isdir(targetdir):
raise OSError('Target directory doesn\'t exist.') raise OSError('Target directory doesn\'t exist.')
if Path(self.basename).suffix.lower() in IMAGE_TYPES:
raise UserWarning('Put images into folder and drag and drop folder into KCC window.')
missing = [] missing = []
extraction_commands = [ extraction_commands = [

View File

@@ -86,9 +86,6 @@ class ProfileData:
] ]
ProfilesKindleEBOK = { ProfilesKindleEBOK = {
}
ProfilesKindlePDOC = {
'K1': ("Kindle 1", (600, 670), Palette4, 1.0), 'K1': ("Kindle 1", (600, 670), Palette4, 1.0),
'K2': ("Kindle 2", (600, 670), Palette15, 1.0), 'K2': ("Kindle 2", (600, 670), Palette15, 1.0),
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.0), 'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.0),
@@ -96,6 +93,9 @@ class ProfileData:
'K57': ("Kindle 5/7", (600, 800), Palette16, 1.0), 'K57': ("Kindle 5/7", (600, 800), Palette16, 1.0),
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.0), 'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.0),
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0), 'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0),
}
ProfilesKindlePDOC = {
'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.0), 'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.0),
'K810': ("Kindle 8/10", (600, 800), Palette16, 1.0), 'K810': ("Kindle 8/10", (600, 800), Palette16, 1.0),
'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0), 'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0),
@@ -263,6 +263,7 @@ class ComicPage:
_, self.size, self.palette, self.gamma = self.opt.profileData _, self.size, self.palette, self.gamma = self.opt.profileData
if self.opt.hq: if self.opt.hq:
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5)) self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
self.kindle_scribe_azw3 = (options.profile == 'KS') and (options.format in ('MOBI', 'EPUB'))
self.original_color_mode = image.mode self.original_color_mode = image.mode
# TODO: color check earlier # TODO: color check earlier
self.image = image.convert("RGB") self.image = image.convert("RGB")
@@ -480,14 +481,6 @@ class ComicPage:
self.image = erase_rainbow_artifacts(self.image, is_color) self.image = erase_rainbow_artifacts(self.image, is_color)
def resizeImage(self): def resizeImage(self):
if self.opt.norotate and self.targetPathOrder in ('-kcc-a', '-kcc-d') and not self.opt.kindle_scribe_azw3:
# TODO: Kindle Scribe case
if self.opt.kindle_azw3 and any(dim > 1920 for dim in self.image.size):
self.image = ImageOps.contain(self.image, (1920, 1920), Image.Resampling.LANCZOS)
elif self.image.size[0] > self.size[0] * 2 or self.image.size[1] > self.size[1]:
self.image = ImageOps.contain(self.image, (self.size[0] * 2, self.size[1]), Image.Resampling.LANCZOS)
return
ratio_device = float(self.size[1]) / float(self.size[0]) ratio_device = float(self.size[1]) / float(self.size[0])
ratio_image = float(self.image.size[1]) / float(self.image.size[0]) ratio_image = float(self.image.size[1]) / float(self.image.size[0])
method = self.resize_method() method = self.resize_method()

View File

@@ -27,9 +27,6 @@ import sys
from traceback import format_tb from traceback import format_tb
IMAGE_TYPES = ('.png', '.jpg', '.jpeg', '.gif', '.webp', '.jp2', '.avif')
class HTMLStripper(HTMLParser): class HTMLStripper(HTMLParser):
def __init__(self): def __init__(self):
HTMLParser.__init__(self) HTMLParser.__init__(self)

View File

@@ -6,6 +6,5 @@ packaging>=23.2
mozjpeg-lossless-optimization>=1.2.0 mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0 natsort>=8.4.0
distro>=1.8.0 distro>=1.8.0
# Below requirements are compiled in Dockefile numpy>=1.22.4
# numpy==2.3.4 PyMuPDF>=1.18.0
# PyMuPDF==1.26.6

View File

@@ -1,4 +1,4 @@
PySide6==6.4.3 PySide6==6.5.2
Pillow>=11.3.0 Pillow>=11.3.0
psutil>=5.9.5 psutil>=5.9.5
requests>=2.31.0 requests>=2.31.0

View File

@@ -2,7 +2,7 @@ PySide6<6.10
Pillow>=11.3.0 Pillow>=11.3.0
psutil>=5.9.5 psutil>=5.9.5
requests>=2.31.0 requests>=2.31.0
python-slugify>=1.2.1,<9.0.0 python-slugify>=1.2.1
raven>=6.0.0 raven>=6.0.0
packaging>=23.2 packaging>=23.2
mozjpeg-lossless-optimization>=1.2.0 mozjpeg-lossless-optimization>=1.2.0

View File

@@ -40,8 +40,8 @@ class BuildBinaryCommand(setuptools.Command):
if sys.platform == 'darwin': if sys.platform == 'darwin':
os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py') os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v # TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET', '') min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET')
if min_os.startswith('10.1'): if min_os:
os.system(f'appdmg kcc.json dist/kcc_osx_{min_os.replace(".", "_")}_legacy_{VERSION}.dmg') os.system(f'appdmg kcc.json dist/kcc_osx_{min_os.replace(".", "_")}_legacy_{VERSION}.dmg')
else: else:
os.system(f'appdmg kcc.json dist/kcc_macos_{platform.processor()}_{VERSION}.dmg') os.system(f'appdmg kcc.json dist/kcc_macos_{platform.processor()}_{VERSION}.dmg')
@@ -148,15 +148,16 @@ setuptools.setup(
}, },
packages=['kindlecomicconverter'], packages=['kindlecomicconverter'],
install_requires=[ install_requires=[
'PySide6>=6.0.0', 'pyside6>=6.0.0',
'Pillow>=9.3.0', 'Pillow>=9.3.0',
'PyMuPDF>=1.18.0',
'psutil>=5.9.5', 'psutil>=5.9.5',
'requests>=2.31.0',
'python-slugify>=1.2.1,<9.0.0', 'python-slugify>=1.2.1,<9.0.0',
'raven>=6.0.0', 'raven>=6.0.0',
'mozjpeg-lossless-optimization>=1.2.0', 'requests>=2.31.0',
'mozjpeg-lossless-optimization>=1.1.2',
'natsort>=8.4.0', 'natsort>=8.4.0',
'distro>=1.8.0', 'distro',
'numpy>=1.22.4', 'numpy>=1.22.4',
'PyMuPDF>=1.16.1', 'PyMuPDF>=1.16.1',
], ],