mirror of
https://github.com/ciromattia/kcc
synced 2026-04-18 15:08:48 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e88dabd1a | ||
|
|
3b7d949128 | ||
|
|
68186285bd | ||
|
|
0abf620698 | ||
|
|
69d3bf3278 |
15
.github/FUNDING.yml
vendored
Normal file
15
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: eink_dude
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
|
polar: # Replace with a single Polar username
|
||||||
|
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||||
|
thanks_dev: # Replace with a single thanks.dev username
|
||||||
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
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 7zip python3-pip squashfs-tools libfuse2 libxcb-cursor0
|
sudo apt-get install -y libpng-dev libjpeg-dev p7zip-full p7zip-rar python3-pip squashfs-tools libfuse2 libxcb-cursor0
|
||||||
python -m pip install --upgrade pip setuptools wheel certifi pyinstaller --no-binary pyinstaller
|
python -m pip install --upgrade pip setuptools wheel certifi pyinstaller --no-binary pyinstaller
|
||||||
python -m pip install -r requirements.txt
|
python -m pip install -r requirements.txt
|
||||||
- name: build binary
|
- name: build binary
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ RUN echo "I'm building for $TARGETOS/$TARGETARCH/$TARGETVARIANT"
|
|||||||
COPY requirements.txt /opt/kcc/
|
COPY requirements.txt /opt/kcc/
|
||||||
ENV PATH="/opt/venv/bin:$PATH"
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||||
apt-get install -y libpng-dev libjpeg-dev 7zip unrar-free libgl1 && \
|
apt-get install -y libpng-dev libjpeg-dev p7zip-full unrar-free libgl1 && \
|
||||||
python -m pip install --upgrade pip && \
|
python -m pip install --upgrade pip && \
|
||||||
python -m venv /opt/venv && \
|
python -m venv /opt/venv && \
|
||||||
python -m pip install -r /opt/kcc/requirements.txt
|
python -m pip install -r /opt/kcc/requirements.txt
|
||||||
@@ -55,7 +55,7 @@ RUN set -x && \
|
|||||||
KEPT_PACKAGES+=(locales-all) && \
|
KEPT_PACKAGES+=(locales-all) && \
|
||||||
KEPT_PACKAGES+=(libfreetype6) && \
|
KEPT_PACKAGES+=(libfreetype6) && \
|
||||||
KEPT_PACKAGES+=(libfontconfig1) && \
|
KEPT_PACKAGES+=(libfontconfig1) && \
|
||||||
KEPT_PACKAGES+=(7zip) && \
|
KEPT_PACKAGES+=(p7zip-full) && \
|
||||||
KEPT_PACKAGES+=(python3) && \
|
KEPT_PACKAGES+=(python3) && \
|
||||||
KEPT_PACKAGES+=(python3-pip) && \
|
KEPT_PACKAGES+=(python3-pip) && \
|
||||||
KEPT_PACKAGES+=(unrar-free) && \
|
KEPT_PACKAGES+=(unrar-free) && \
|
||||||
@@ -113,7 +113,7 @@ RUN set -x && \
|
|||||||
KEPT_PACKAGES+=(locales-all) && \
|
KEPT_PACKAGES+=(locales-all) && \
|
||||||
KEPT_PACKAGES+=(libfreetype6) && \
|
KEPT_PACKAGES+=(libfreetype6) && \
|
||||||
KEPT_PACKAGES+=(libfontconfig1) && \
|
KEPT_PACKAGES+=(libfontconfig1) && \
|
||||||
KEPT_PACKAGES+=(7zip) && \
|
KEPT_PACKAGES+=(p7zip-full) && \
|
||||||
KEPT_PACKAGES+=(python3) && \
|
KEPT_PACKAGES+=(python3) && \
|
||||||
KEPT_PACKAGES+=(python3-pip) && \
|
KEPT_PACKAGES+=(python3-pip) && \
|
||||||
KEPT_PACKAGES+=(unrar-free) && \
|
KEPT_PACKAGES+=(unrar-free) && \
|
||||||
@@ -158,7 +158,7 @@ LABEL org.opencontainers.image.title="Kindle Comic Converter"
|
|||||||
ENV PATH="/opt/venv/bin:$PATH"
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update -y && apt-get -yq upgrade && \
|
||||||
apt-get install -y 7zip unrar-free && \
|
apt-get install -y p7zip-full unrar-free && \
|
||||||
ln -s /app/kindlegen /bin/kindlegen && \
|
ln -s /app/kindlegen /bin/kindlegen && \
|
||||||
echo docker-base-20241116 > /IMAGE_VERSION
|
echo docker-base-20241116 > /IMAGE_VERSION
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more detail
|
|||||||
CLI version of **KCC** is intended for power users. It allows using options that might not be compatible and decrease the quality of output.
|
CLI version of **KCC** is intended for power users. It allows using options that might not be compatible and decrease the quality of output.
|
||||||
CLI version has reduced dependencies, on Debian based distributions this commands should install all needed dependencies:
|
CLI version has reduced dependencies, on Debian based distributions this commands should install all needed dependencies:
|
||||||
```
|
```
|
||||||
sudo apt-get install python3 7zip python3-pil python3-psutil python3-slugify
|
sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugify
|
||||||
```
|
```
|
||||||
|
|
||||||
### Profiles:
|
### Profiles:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '8.0.0'
|
__version__ = '8.0.1'
|
||||||
__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'
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ from tempfile import mkdtemp, gettempdir, TemporaryFile
|
|||||||
from shutil import move, copytree, rmtree, copyfile
|
from shutil import move, copytree, rmtree, copyfile
|
||||||
from multiprocessing import Pool
|
from multiprocessing import Pool
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from natsort import os_sort_keygen
|
from natsort import os_sort_keygen, os_sorted
|
||||||
from slugify import slugify as slugify_ext
|
from slugify import slugify as slugify_ext
|
||||||
from PIL import Image, ImageFile
|
from PIL import Image, ImageFile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -691,16 +691,6 @@ def getWorkFolder(afile):
|
|||||||
cbx = comicarchive.ComicArchive(afile)
|
cbx = comicarchive.ComicArchive(afile)
|
||||||
path = cbx.extract(workdir)
|
path = cbx.extract(workdir)
|
||||||
sanitizePermissions(path)
|
sanitizePermissions(path)
|
||||||
tdir = os.listdir(workdir)
|
|
||||||
if len(tdir) == 2 and 'ComicInfo.xml' in tdir:
|
|
||||||
tdir.remove('ComicInfo.xml')
|
|
||||||
if os.path.isdir(os.path.join(workdir, tdir[0])):
|
|
||||||
os.replace(
|
|
||||||
os.path.join(workdir, 'ComicInfo.xml'),
|
|
||||||
os.path.join(workdir, tdir[0], 'ComicInfo.xml')
|
|
||||||
)
|
|
||||||
if len(tdir) == 1 and os.path.isdir(os.path.join(workdir, tdir[0])):
|
|
||||||
path = os.path.join(workdir, tdir[0])
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
rmtree(workdir, True)
|
rmtree(workdir, True)
|
||||||
raise UserWarning(e)
|
raise UserWarning(e)
|
||||||
@@ -824,6 +814,9 @@ def getPanelViewSize(deviceres, size):
|
|||||||
|
|
||||||
|
|
||||||
def removeNonImages(filetree):
|
def removeNonImages(filetree):
|
||||||
|
# clean dot from original file
|
||||||
|
dot_clean(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)
|
||||||
@@ -841,24 +834,27 @@ def sanitizeTree(filetree):
|
|||||||
page = 1
|
page = 1
|
||||||
cover_path = None
|
cover_path = None
|
||||||
for root, dirs, files in os.walk(filetree):
|
for root, dirs, files in os.walk(filetree):
|
||||||
dirs.sort(key=OS_SORT_KEY)
|
|
||||||
files.sort(key=OS_SORT_KEY)
|
files.sort(key=OS_SORT_KEY)
|
||||||
for name in files:
|
for name in files:
|
||||||
_, ext = getImageFileName(name)
|
_, ext = getImageFileName(name)
|
||||||
|
|
||||||
# 9999 page limit
|
# 9999 page limit
|
||||||
slugified = f'kcc-{page:04}'
|
unique_name = f'kcc-{page:04}'
|
||||||
page += 1
|
page += 1
|
||||||
|
|
||||||
newKey = os.path.join(root, slugified + ext)
|
newKey = os.path.join(root, unique_name + ext)
|
||||||
key = os.path.join(root, name)
|
key = os.path.join(root, name)
|
||||||
if key != newKey:
|
if key != newKey:
|
||||||
os.replace(key, newKey)
|
os.replace(key, newKey)
|
||||||
if not cover_path:
|
if not cover_path:
|
||||||
cover_path = newKey
|
cover_path = newKey
|
||||||
|
is_natural_sorted = False
|
||||||
|
if os_sorted(dirs) == sorted(dirs):
|
||||||
|
is_natural_sorted = True
|
||||||
|
dirs.sort(key=OS_SORT_KEY)
|
||||||
for i, name in enumerate(dirs):
|
for i, name in enumerate(dirs):
|
||||||
tmpName = name
|
tmpName = name
|
||||||
slugified = slugify(name)
|
slugified = slugify(name, is_natural_sorted)
|
||||||
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
|
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
|
||||||
slugified += "A"
|
slugified += "A"
|
||||||
chapterNames[slugified] = tmpName
|
chapterNames[slugified] = tmpName
|
||||||
@@ -884,8 +880,7 @@ def sanitizePermissions(filetree):
|
|||||||
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD)
|
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD)
|
||||||
for name in dirs:
|
for name in dirs:
|
||||||
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC)
|
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC)
|
||||||
# clean dot from original file
|
|
||||||
dot_clean(filetree)
|
|
||||||
|
|
||||||
def dot_clean(filetree):
|
def dot_clean(filetree):
|
||||||
for root, _, files in os.walk(filetree, topdown=False):
|
for root, _, files in os.walk(filetree, topdown=False):
|
||||||
@@ -1026,11 +1021,15 @@ def createNewTome(parent):
|
|||||||
return tomePath, tomePathRoot
|
return tomePath, tomePathRoot
|
||||||
|
|
||||||
|
|
||||||
def slugify(value):
|
def slugify(value, is_natural_sorted):
|
||||||
if options.format == 'CBZ':
|
if options.format == 'CBZ' and is_natural_sorted:
|
||||||
return value
|
return value
|
||||||
value = slugify_ext(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
|
if options.format != 'CBZ':
|
||||||
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
# convert all unicode to ascii via slugify
|
||||||
|
value = slugify_ext(value, regex_pattern=r'[^-a-z0-9_\.]+').strip('.')
|
||||||
|
if not is_natural_sorted:
|
||||||
|
# pad zeros to numbers
|
||||||
|
value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value, count=2))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from xml.parsers.expat import ExpatError
|
|||||||
from .shared import 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 = '7z' if os.name == 'nt' else '7zz'
|
SEVENZIP = '7zz' if platform.system() == 'Darwin' else '7z'
|
||||||
|
|
||||||
|
|
||||||
class ComicArchive:
|
class ComicArchive:
|
||||||
|
|||||||
Reference in New Issue
Block a user