84 Commits

Author SHA1 Message Date
renovate[bot]
ea8fd55cc2 fix(deps): update module github.com/samber/lo to v1.49.0 (#40)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-27 04:30:54 +00:00
renovate[bot]
709c53d647 fix(deps): update module github.com/pablodz/inotifywaitgo to v0.0.9 (#39)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-26 20:57:06 +00:00
renovate[bot]
919a53fec7 fix(deps): update module github.com/samber/lo to v1.48.0 (#38)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-26 10:22:50 +00:00
renovate[bot]
d3b3a73b8f chore(deps): update anchore/sbom-action action to v0.18.0 (#37)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 20:47:16 +00:00
renovate[bot]
188211e26d fix(deps): update golang.org/x/exp digest to 7588d65 (#36)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-06 21:12:16 +00:00
renovate[bot]
f57a88eaf4 fix(deps): update module github.com/thediveo/enumflag/v2 to v2.0.7 (#35)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-05 19:29:38 +00:00
renovate[bot]
6e6b66b5eb fix(deps): update golang.org/x/exp digest to 7d7fa50 (#34)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 21:05:57 +00:00
renovate[bot]
1ff1bed3cc fix(deps): update golang.org/x/exp digest to dd03c70 (#33)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-03 18:34:17 +00:00
renovate[bot]
196938718c fix(deps): update golang.org/x/exp digest to b2144cd (#32)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-17 20:15:57 +00:00
renovate[bot]
9972709d32 fix(deps): update golang.org/x/exp digest to 4a55095 (#31)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-15 18:36:01 +00:00
renovate[bot]
152fa85577 chore(deps): update anchore/sbom-action action to v0.17.9 (#30)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-13 23:01:28 +00:00
renovate[bot]
554fce5d1e fix(deps): update golang.org/x/exp digest to 1829a12 (#29)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-10 23:09:05 +00:00
renovate[bot]
25357e9ec6 fix(deps): update golang.org/x/exp digest to 1443442 (#28)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-10 17:58:23 +00:00
Antoine Aflalo
ecc561263f fix: move image to debian 2024-12-06 18:00:24 -05:00
Antoine Aflalo
fb1056e5e7 ci: remove bash completion 2024-12-06 17:40:42 -05:00
Antoine Aflalo
07bc88bb04 fix: v2 versioning 2024-12-06 17:28:09 -05:00
Antoine Aflalo
8c3665fa53 ci: fix dockerfile 2024-12-06 17:26:25 -05:00
Antoine Aflalo
8dce346997 ci: debug release 2 2024-12-06 17:22:27 -05:00
Antoine Aflalo
4646789e4e ci: debug release 2024-12-06 17:21:12 -05:00
Antoine Aflalo
22ca56c98b ci: fix building 2024-12-06 17:18:28 -05:00
Antoine Aflalo
f45a1d4ed0 ci: remove arm64 2024-12-06 17:10:52 -05:00
Antoine Aflalo
ee53fddf02 ci: fix version 2024-12-06 17:03:42 -05:00
Antoine Aflalo
f416f1ff32 feat: replace webp lib by C libwebp
Avoid having to download anything
2024-12-06 17:01:00 -05:00
renovate[bot]
969993161f fix(deps): update golang.org/x/exp digest to 43b7b7c (#27)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-05 01:57:59 +00:00
renovate[bot]
f6b41f6391 fix(deps): update module golang.org/x/image to v0.23.0 (#26)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-04 18:33:59 +00:00
renovate[bot]
0bb9e4320c chore(deps): update anchore/sbom-action action to v0.17.8 (#25)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-21 18:47:50 +00:00
Antoine Aflalo
35cfe41aa6 Merge pull request #24 from Belphemur/renovate/codecov-codecov-action-5.x
chore(deps): update codecov/codecov-action action to v5
2024-11-18 14:37:12 -05:00
renovate[bot]
021c647a6e chore(deps): update codecov/codecov-action action to v5 2024-11-14 19:14:51 +00:00
renovate[bot]
6217254305 fix(deps): update golang.org/x/exp digest to 2d47ceb (#23)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 23:01:56 +00:00
renovate[bot]
0ad711a24d fix(deps): update golang.org/x/exp digest to 04b2079 (#22)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 19:25:44 +00:00
renovate[bot]
f24e4cc26e fix(deps): update module golang.org/x/image to v0.22.0 (#21)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 00:06:54 +00:00
Antoine Aflalo
1d3a8396f2 Merge pull request #20 from Belphemur/renovate/anchore-sbom-action-0.x 2024-11-05 09:49:07 -05:00
renovate[bot]
497f206c50 chore(deps): update anchore/sbom-action action to v0.17.7 2024-11-05 14:37:06 +00:00
renovate[bot]
9ade876952 chore(deps): update anchore/sbom-action action to v0.17.6 (#19)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-29 16:46:36 +00:00
Antoine Aflalo
103d38c74b Merge pull request #18 from Belphemur/renovate/anchore-sbom-action-0.x 2024-10-22 10:23:05 -04:00
renovate[bot]
80a1afe7c3 chore(deps): update anchore/sbom-action action to v0.17.5 2024-10-21 20:34:41 +00:00
Antoine Aflalo
2de7bc7a04 Merge pull request #17 from Belphemur/renovate/anchore-sbom-action-0.x 2024-10-15 14:29:56 -04:00
renovate[bot]
bccf7a7029 chore(deps): update anchore/sbom-action action to v0.17.4 2024-10-15 17:14:00 +00:00
renovate[bot]
4e80ddfb3a chore(deps): update anchore/sbom-action action to v0.17.3 (#16)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-12 00:29:35 +00:00
renovate[bot]
090bbac593 fix(deps): update golang.org/x/exp digest to f66d83c (#15)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-09 22:19:26 +00:00
renovate[bot]
c8b0f11784 fix(deps): update golang.org/x/exp digest to 225e2ab (#14)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-05 00:22:41 +00:00
Antoine Aflalo
449b57b14e Merge pull request #13 from Belphemur/renovate/golang.org-x-image-0.x
fix(deps): update module golang.org/x/image to v0.21.0
2024-10-04 14:25:05 -04:00
renovate[bot]
0dcb55f06d fix(deps): update module golang.org/x/image to v0.21.0 2024-10-04 16:40:30 +00:00
Antoine Aflalo
33ae460caf Merge pull request #12 from Belphemur/renovate/sigstore-cosign-installer-3.x
chore(deps): update sigstore/cosign-installer action to v3.7.0
2024-10-04 09:57:48 -04:00
renovate[bot]
1af484aea8 chore(deps): update sigstore/cosign-installer action to v3.7.0 2024-10-04 13:31:05 +00:00
Antoine Aflalo
e798a59a43 perf: fix any unhandled errors 2024-09-10 15:10:40 -04:00
Antoine Aflalo
72086d658e refactor: clean up 2024-09-10 13:58:52 -04:00
Antoine Aflalo
a7bca7ee05 ci(qodana): add qodana 2024-09-10 13:54:08 -04:00
Antoine Aflalo
ba82003b53 Merge pull request #11 from Belphemur/renovate
perf(error): better deal with deferred errors
2024-09-09 14:47:12 -04:00
Antoine Aflalo
5f7e7de644 ci(tests): fix possible error with tests 2024-09-09 14:45:30 -04:00
Antoine Aflalo
5b183cca29 perf(error): better deal with deferred errors 2024-09-09 14:45:30 -04:00
Antoine Aflalo
d901be14fa Merge pull request #9 from Belphemur/renovatebot
ci(renovate): auto merge digest
2024-09-09 14:11:56 -04:00
Antoine Aflalo
a80997835a chore(deps): update deps 2024-09-09 14:09:39 -04:00
Antoine Aflalo
37bb12fd61 ci(renovate): auto merge digest 2024-09-09 14:09:23 -04:00
Antoine Aflalo
c19afb9f40 Merge pull request #7 from Belphemur/renovate/golang.org-x-exp-digest
fix(deps): update golang.org/x/exp digest to 701f63a
2024-09-09 14:06:57 -04:00
renovate[bot]
911e1041ff fix(deps): update golang.org/x/exp digest to 701f63a 2024-09-09 18:06:01 +00:00
Antoine Aflalo
a10d589b67 Merge pull request #8 from Belphemur/fix-ci
ci: always generate and upload test results
2024-09-09 14:05:05 -04:00
Antoine Aflalo
da508fcb3f ci: always generate and upload test results 2024-09-09 14:03:28 -04:00
Antoine Aflalo
57f5282032 ci(renovate): add automerge 2024-09-09 09:27:33 -04:00
Antoine Aflalo
d4f8d8b5ff ci(test): fix the report xml file 2024-09-09 09:25:46 -04:00
Antoine Aflalo
1b026b9dbd fix(watch): add missing split option in log 2024-09-09 09:11:45 -04:00
Antoine Aflalo
12cc8d4e25 Merge pull request #6 from Belphemur/renovate/golang.org-x-exp-digest
fix(deps): update golang.org/x/exp digest to e7e105d
2024-09-06 17:10:42 -04:00
renovate[bot]
3442b2a845 fix(deps): update golang.org/x/exp digest to e7e105d 2024-09-06 21:08:46 +00:00
Antoine Aflalo
b9a1fb213a Merge pull request #5 from Belphemur/renovate/golang.org-x-image-0.x
fix(deps): update module golang.org/x/image to v0.20.0
2024-09-06 17:07:40 -04:00
renovate[bot]
278ee130e3 fix(deps): update module golang.org/x/image to v0.20.0 2024-09-04 19:30:08 +00:00
Antoine Aflalo
5357ece2b7 perf: use comment of the zip to know if it's converted instead of txt file 2024-08-29 09:38:39 -04:00
Antoine Aflalo
dbef43d376 fix(watch): fix watch command not using proper path 2024-08-28 15:06:47 -04:00
Antoine Aflalo
7c63ea49c0 fix(docker): fix docker image config folder 2024-08-28 14:36:14 -04:00
Antoine Aflalo
8a067939af Merge pull request #4 from Belphemur/renovate/major-github-artifact-actions
chore(deps): update actions/upload-artifact action to v4
2024-08-28 14:24:57 -04:00
Antoine Aflalo
f89974ac79 ci: Another attempt at reducing 2024-08-28 14:22:25 -04:00
Antoine Aflalo
ce365a6bdf ci: reduce size of page to pass tests
Fix failing test
2024-08-28 14:16:51 -04:00
renovate[bot]
9e61ff4634 chore(deps): update actions/upload-artifact action to v4 2024-08-28 17:56:03 +00:00
Antoine Aflalo
63a1b592c3 ci: add test result to pipeline 2024-08-28 13:55:33 -04:00
Antoine Aflalo
673484692b perf(webp): improve the error message for page too tall 2024-08-28 13:52:27 -04:00
Antoine Aflalo
ad35e2655f feat(webp): add partial success to conversion
So we only keep images that couldn't be optimized and return the chapter
2024-08-28 13:49:14 -04:00
Antoine Aflalo
d7f55fa886 fix(webp): improve error message in page not convertible 2024-08-28 12:09:40 -04:00
Antoine Aflalo
62638517e4 test: improve testing suite for expected failure 2024-08-28 12:03:33 -04:00
Antoine Aflalo
dbf7f6c262 fix(webp): be sure we split big page when requested 2024-08-28 11:55:53 -04:00
Antoine Aflalo
9ecd5ff3a5 fix(webp): fix the actual maximum limit 2024-08-28 11:53:26 -04:00
Antoine Aflalo
a63d2395f0 fix(webp): better handling of error for page too big for webp 2024-08-28 11:51:06 -04:00
Antoine Aflalo
839ad9ed9d fix(cbz): make pages be the first in the cbz by only be number 2024-08-28 09:16:19 -04:00
Antoine Aflalo
c8879349e1 feat(split): Make the split configurable for the watch command 2024-08-28 09:10:08 -04:00
Antoine Aflalo
5ac59a93c5 feat(split): Make the split configurable for the optimize command 2024-08-28 09:06:49 -04:00
Antoine Aflalo
72c6776793 fix: make the progress more readable 2024-08-27 20:42:26 -04:00
28 changed files with 635 additions and 256 deletions

23
.github/workflows/qodana.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
jobs:
qodana:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
fetch-depth: 0 # a full history is required for pull request analysis
- name: 'Qodana Scan'
uses: JetBrains/qodana-action@v2024.1
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}

View File

@@ -22,12 +22,14 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # this is important, otherwise it won't checkout the full tree (i.e. no previous tags)
- name: Install libwebp-dev
run: sudo apt-get install -y libwebp-dev
- uses: actions/setup-go@v5
with:
go-version: 1.23
cache: true
- uses: sigstore/cosign-installer@v3.6.0 # installs cosign
- uses: anchore/sbom-action/download-syft@v0.17.2 # installs syft
- uses: sigstore/cosign-installer@v3.7.0 # installs cosign
- uses: anchore/sbom-action/download-syft@v0.18.0 # installs syft
- uses: docker/login-action@v3 # login to ghcr
with:
registry: ghcr.io

View File

@@ -17,6 +17,9 @@ jobs:
with:
go-version: '1.23'
- name: Install libwebp-dev
run: sudo apt-get install -y libwebp-dev
- name: Install dependencies
run: go mod tidy
@@ -28,9 +31,23 @@ jobs:
mv go-junit-report /usr/local/bin/
- name: Run tests
run: go test -v 2>&1 ./... -coverprofile=coverage.txt | go-junit-report -set-exit-code > junit.xml
run: |
set -o pipefail
go test -v 2>&1 ./... -coverprofile=coverage.txt | tee test-results.txt
- name: Analyse test results
if: ${{ !cancelled() }}
run: go-junit-report < test-results.txt > junit.xml
- name: Upload test result artifact
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
test-results.txt
junit.xml
retention-days: 7
- name: Upload results to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload test results to Codecov

View File

@@ -29,7 +29,6 @@ builds:
- linux
goarch:
- amd64
- arm64
# ensures mod timestamp to be the commit timestamp
mod_timestamp: "{{ .CommitTimestamp }}"
flags:
@@ -38,7 +37,7 @@ builds:
ldflags:
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{ .CommitDate }}
env:
- CGO_ENABLED=0
- CGO_ENABLED=1
# config the checksum filename
# https://goreleaser.com/customization/checksum
checksum:

View File

@@ -1,21 +1,30 @@
FROM alpine:latest
FROM debian:sid-slim
LABEL authors="Belphemur"
ARG APP_PATH=/usr/local/bin/CBZOptimizer
ENV USER=abc
ENV CONFIG_FOLDER=/config
ENV PUID=99
RUN mkdir -p "${CONFIG_FOLDER}" && adduser \
--disabled-password \
--gecos "" \
--home "$(pwd)" \
--ingroup "users" \
RUN mkdir -p "${CONFIG_FOLDER}" && \
useradd \
--system \
--no-create-home \
--home-dir "${CONFIG_FOLDER}" \
--gid "users" \
--uid "${PUID}" \
"${USER}" && \
chown ${PUID}:${GUID} "${CONFIG_FOLDER}"
chown ${PUID}:users "${CONFIG_FOLDER}"
COPY CBZOptimizer /usr/local/bin/CBZOptimizer
COPY CBZOptimizer ${APP_PATH}
RUN apk add --no-cache inotify-tools bash-completion && chmod +x /usr/local/bin/CBZOptimizer && /usr/local/bin/CBZOptimizer completion bash > /etc/bash_completion.d/CBZOptimizer
RUN apt-get update && \
apt-get full-upgrade -y && \
apt-get install -y inotify-tools bash-completion libwebp7 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
chmod +x ${APP_PATH} && \
${APP_PATH} completion bash > /etc/bash_completion.d/CBZOptimizer
VOLUME ${CONFIG_FOLDER}
USER ${USER}
ENTRYPOINT ["/usr/local/bin/CBZOptimizer"]

View File

@@ -1,3 +1,4 @@
# CBZOptimizer
CBZOptimizer is a Go-based tool designed to optimize CBZ (Comic Book Zip) files by converting images to a specified format and quality. This tool is useful for reducing the size of comic book archives while maintaining acceptable image quality.
@@ -8,14 +9,15 @@ CBZOptimizer is a Go-based tool designed to optimize CBZ (Comic Book Zip) files
- Adjust the quality of the converted images.
- Process multiple chapters in parallel.
- Option to override the original CBZ files.
- Watch a folder for new CBZ files and optimize them automatically.
## Installation
1. Clone the repository:
```sh
git clone https://github.com/belphemur/CBZOptimizer.git
cd CBZOptimizer
```
```sh
git clone https://github.com/belphemur/CBZOptimizer.git
cd CBZOptimizer
```
2. Install dependencies:
```sh
@@ -26,10 +28,22 @@ CBZOptimizer is a Go-based tool designed to optimize CBZ (Comic Book Zip) files
### Command Line Interface
The tool provides a CLI command to optimize CBZ files. Below is an example of how to use it:
The tool provides CLI commands to optimize and watch CBZ files. Below are examples of how to use them:
#### Optimize Command
Optimize all CBZ files in a folder recursively:
```sh
go run main.go optimize --quality 85 --parallelism 2 --override /path/to/cbz/files
go run main.go optimize [folder] --quality 85 --parallelism 2 --override --format webp --split
```
#### Watch Command
Watch a folder for new CBZ files and optimize them automatically:
```sh
go run main.go watch [folder] --quality 85 --override --format webp --split
```
### Flags
@@ -37,6 +51,8 @@ go run main.go optimize --quality 85 --parallelism 2 --override /path/to/cbz/fil
- `--quality`, `-q`: Quality for conversion (0-100). Default is 85.
- `--parallelism`, `-n`: Number of chapters to convert in parallel. Default is 2.
- `--override`, `-o`: Override the original CBZ files. Default is false.
- `--split`, `-s`: Split long pages into smaller chunks. Default is false.
- `--format`, `-f`: Format to convert the images to (e.g., webp). Default is webp.
## Testing
@@ -46,6 +62,12 @@ To run the tests, use the following command:
go test ./... -v
```
## Requirement
Needs to have libwep installed on the machine if you're not using the docker image
## Docker
`ghcr.io/belphemur/cbzoptimizer:latest`
## GitHub Actions
The project includes a GitHub Actions workflow to run tests on every push and pull request to the `main` branch. The workflow is defined in `.github/workflows/go.yml`.

View File

@@ -3,7 +3,8 @@ package cbz
import (
"archive/zip"
"fmt"
"github.com/belphemur/CBZOptimizer/manga"
"github.com/belphemur/CBZOptimizer/v2/manga"
"github.com/belphemur/CBZOptimizer/v2/utils/errs"
"os"
"time"
)
@@ -14,15 +15,14 @@ func WriteChapterToCBZ(chapter *manga.Chapter, outputFilePath string) error {
if err != nil {
return fmt.Errorf("failed to create .cbz file: %w", err)
}
defer zipFile.Close()
defer errs.Capture(&err, zipFile.Close, "failed to close .cbz file")
// Create a new ZIP writer
zipWriter := zip.NewWriter(zipFile)
err = zipWriter.SetComment("Created by CBZOptimizer")
if err != nil {
return err
}
defer zipWriter.Close()
defer errs.Capture(&err, zipWriter.Close, "failed to close .cbz writer")
// Write each page to the ZIP archive
for _, page := range chapter.Pages {
@@ -30,10 +30,10 @@ func WriteChapterToCBZ(chapter *manga.Chapter, outputFilePath string) error {
var fileName string
if page.IsSplitted {
// Use the format page%03d-%02d for split pages
fileName = fmt.Sprintf("page_%04d-%02d%s", page.Index, page.SplitPartIndex, page.Extension)
fileName = fmt.Sprintf("%04d-%02d%s", page.Index, page.SplitPartIndex, page.Extension)
} else {
// Use the format page%03d for non-split pages
fileName = fmt.Sprintf("page_%04d%s", page.Index, page.Extension)
fileName = fmt.Sprintf("%04d%s", page.Index, page.Extension)
}
// Create a new file in the ZIP archive
@@ -71,18 +71,11 @@ func WriteChapterToCBZ(chapter *manga.Chapter, outputFilePath string) error {
}
if chapter.IsConverted {
convertedWriter, err := zipWriter.CreateHeader(&zip.FileHeader{
Name: "Converted.txt",
Method: zip.Deflate,
Modified: time.Now(),
})
if err != nil {
return fmt.Errorf("failed to create Converted.txt in .cbz: %w", err)
}
_, err = convertedWriter.Write([]byte(fmt.Sprintf("%s\nThis chapter has been converted by CBZOptimizer.", chapter.ConvertedTime)))
convertedString := fmt.Sprintf("%s\nThis chapter has been converted by CBZOptimizer.", chapter.ConvertedTime)
err = zipWriter.SetComment(convertedString)
if err != nil {
return fmt.Errorf("failed to write Converted.txt contents: %w", err)
return fmt.Errorf("failed to write comment: %w", err)
}
}

View File

@@ -3,18 +3,23 @@ package cbz
import (
"archive/zip"
"bytes"
"github.com/belphemur/CBZOptimizer/manga"
"fmt"
"github.com/belphemur/CBZOptimizer/v2/manga"
"github.com/belphemur/CBZOptimizer/v2/utils/errs"
"os"
"testing"
"time"
)
func TestWriteChapterToCBZ(t *testing.T) {
currentTime := time.Now()
// Define test cases
testCases := []struct {
name string
chapter *manga.Chapter
expectedFiles []string
expectedComment string
}{
//test case where there is only one page and ComicInfo and the chapter is converted
{
@@ -29,9 +34,10 @@ func TestWriteChapterToCBZ(t *testing.T) {
},
ComicInfoXml: "<Series>Boundless Necromancer</Series>",
IsConverted: true,
ConvertedTime: time.Now(),
ConvertedTime: currentTime,
},
expectedFiles: []string{"page_0000.jpg", "ComicInfo.xml", "Converted.txt"},
expectedFiles: []string{"0000.jpg", "ComicInfo.xml"},
expectedComment: fmt.Sprintf("%s\nThis chapter has been converted by CBZOptimizer.", currentTime),
},
//test case where there is only one page and no
{
@@ -45,7 +51,7 @@ func TestWriteChapterToCBZ(t *testing.T) {
},
},
},
expectedFiles: []string{"page_0000.jpg"},
expectedFiles: []string{"0000.jpg"},
},
{
name: "Multiple pages with ComicInfo",
@@ -64,7 +70,7 @@ func TestWriteChapterToCBZ(t *testing.T) {
},
ComicInfoXml: "<Series>Boundless Necromancer</Series>",
},
expectedFiles: []string{"page_0000.jpg", "page_0001.jpg", "ComicInfo.xml"},
expectedFiles: []string{"0000.jpg", "0001.jpg", "ComicInfo.xml"},
},
{
name: "Split page",
@@ -79,7 +85,7 @@ func TestWriteChapterToCBZ(t *testing.T) {
},
},
},
expectedFiles: []string{"page_0000-01.jpg"},
expectedFiles: []string{"0000-01.jpg"},
},
}
@@ -90,7 +96,7 @@ func TestWriteChapterToCBZ(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create temporary file: %v", err)
}
defer os.Remove(tempFile.Name())
defer errs.CaptureGeneric(&err, os.Remove, tempFile.Name(), "failed to remove temporary file")
// Write the chapter to the .cbz file
err = WriteChapterToCBZ(tc.chapter, tempFile.Name())
@@ -103,7 +109,7 @@ func TestWriteChapterToCBZ(t *testing.T) {
if err != nil {
t.Fatalf("Failed to open CBZ file: %v", err)
}
defer r.Close()
defer errs.Capture(&err, r.Close, "failed to close CBZ file")
// Collect the names of the files in the archive
var filesInArchive []string
@@ -125,6 +131,10 @@ func TestWriteChapterToCBZ(t *testing.T) {
}
}
if tc.expectedComment != "" && r.Comment != tc.expectedComment {
t.Errorf("Expected comment %s, but found %s", tc.expectedComment, r.Comment)
}
// Check if there are no unexpected files
if len(filesInArchive) != len(tc.expectedFiles) {
t.Errorf("Expected %d files, but found %d", len(tc.expectedFiles), len(filesInArchive))

View File

@@ -6,7 +6,8 @@ import (
"bytes"
"fmt"
"github.com/araddon/dateparse"
"github.com/belphemur/CBZOptimizer/manga"
"github.com/belphemur/CBZOptimizer/v2/manga"
"github.com/belphemur/CBZOptimizer/v2/utils/errs"
"io"
"path/filepath"
"strings"
@@ -18,22 +19,36 @@ func LoadChapter(filePath string) (*manga.Chapter, error) {
if err != nil {
return nil, fmt.Errorf("failed to open .cbz file: %w", err)
}
defer r.Close()
defer errs.Capture(&err, r.Close, "failed to close opened .cbz file")
chapter := &manga.Chapter{
FilePath: filePath,
}
// Check for comment
if r.Comment != "" {
scanner := bufio.NewScanner(strings.NewReader(r.Comment))
if scanner.Scan() {
convertedTime := scanner.Text()
chapter.ConvertedTime, err = dateparse.ParseAny(convertedTime)
if err == nil {
chapter.IsConverted = true
}
}
}
for _, f := range r.File {
if f.FileInfo().IsDir() {
continue
}
err := func() error {
// Open the file inside the zip
rc, err := f.Open()
if err != nil {
return nil, fmt.Errorf("failed to open file inside .cbz: %w", err)
return fmt.Errorf("failed to open file inside .cbz: %w", err)
}
defer errs.Capture(&err, rc.Close, "failed to close file inside .cbz")
// Determine the file extension
ext := strings.ToLower(filepath.Ext(f.Name))
@@ -41,23 +56,20 @@ func LoadChapter(filePath string) (*manga.Chapter, error) {
// Read the ComicInfo.xml file content
xmlContent, err := io.ReadAll(rc)
if err != nil {
rc.Close()
return nil, fmt.Errorf("failed to read ComicInfo.xml content: %w", err)
return fmt.Errorf("failed to read ComicInfo.xml content: %w", err)
}
chapter.ComicInfoXml = string(xmlContent)
} else if ext == ".txt" && strings.ToLower(filepath.Base(f.Name)) == "converted.txt" {
} else if !chapter.IsConverted && ext == ".txt" && strings.ToLower(filepath.Base(f.Name)) == "converted.txt" {
textContent, err := io.ReadAll(rc)
if err != nil {
rc.Close()
return nil, fmt.Errorf("failed to read Converted.xml content: %w", err)
return fmt.Errorf("failed to read Converted.xml content: %w", err)
}
scanner := bufio.NewScanner(bytes.NewReader(textContent))
if scanner.Scan() {
convertedTime := scanner.Text()
chapter.ConvertedTime, err = dateparse.ParseAny(convertedTime)
if err != nil {
rc.Close()
return nil, fmt.Errorf("failed to parse converted time: %w", err)
return fmt.Errorf("failed to parse converted time: %w", err)
}
chapter.IsConverted = true
}
@@ -66,8 +78,7 @@ func LoadChapter(filePath string) (*manga.Chapter, error) {
buf := new(bytes.Buffer)
_, err = io.Copy(buf, rc)
if err != nil {
rc.Close()
return nil, fmt.Errorf("failed to read file contents: %w", err)
return fmt.Errorf("failed to read file contents: %w", err)
}
// Create a new Page object
@@ -82,8 +93,11 @@ func LoadChapter(filePath string) (*manga.Chapter, error) {
// Add the page to the chapter
chapter.Pages = append(chapter.Pages, page)
}
rc.Close()
return nil
}()
if err != nil {
return nil, err
}
}
return chapter, nil

View File

@@ -24,7 +24,7 @@ func TestLoadChapter(t *testing.T) {
},
{
name: "Converted Chapter",
filePath: "../testdata/Chapter 1_converted.cbz",
filePath: "../testdata/Chapter 10_converted.cbz",
expectedPages: 107,
expectedSeries: "<Series>Boundless Necromancer</Series>",
expectedConversion: true,

View File

@@ -2,9 +2,9 @@ package cmd
import (
"fmt"
"github.com/belphemur/CBZOptimizer/converter"
"github.com/belphemur/CBZOptimizer/converter/constant"
"github.com/belphemur/CBZOptimizer/utils"
"github.com/belphemur/CBZOptimizer/v2/converter"
"github.com/belphemur/CBZOptimizer/v2/converter/constant"
"github.com/belphemur/CBZOptimizer/v2/utils"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
"os"
@@ -29,6 +29,7 @@ func init() {
command.Flags().Uint8P("quality", "q", 85, "Quality for conversion (0-100)")
command.Flags().IntP("parallelism", "n", 2, "Number of chapters to convert in parallel")
command.Flags().BoolP("override", "o", false, "Override the original CBZ files")
command.Flags().BoolP("split", "s", false, "Split long pages into smaller chunks")
command.PersistentFlags().VarP(
formatFlag,
"format", "f",
@@ -58,6 +59,11 @@ func ConvertCbzCommand(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid quality value")
}
split, err := cmd.Flags().GetBool("split")
if err != nil {
return fmt.Errorf("invalid split value")
}
parallelism, err := cmd.Flags().GetInt("parallelism")
if err != nil || parallelism < 1 {
return fmt.Errorf("invalid parallelism value")
@@ -86,7 +92,13 @@ func ConvertCbzCommand(cmd *cobra.Command, args []string) error {
go func() {
defer wg.Done()
for path := range fileChan {
err := utils.Optimize(chapterConverter, path, quality, override)
err := utils.Optimize(&utils.OptimizeOptions{
ChapterConverter: chapterConverter,
Path: path,
Quality: quality,
Override: override,
Split: split,
})
if err != nil {
errorChan <- fmt.Errorf("error processing file %s: %w", path, err)
}

View File

@@ -1,10 +1,11 @@
package cmd
import (
"github.com/belphemur/CBZOptimizer/cbz"
"github.com/belphemur/CBZOptimizer/converter"
"github.com/belphemur/CBZOptimizer/converter/constant"
"github.com/belphemur/CBZOptimizer/manga"
"github.com/belphemur/CBZOptimizer/v2/cbz"
"github.com/belphemur/CBZOptimizer/v2/converter"
"github.com/belphemur/CBZOptimizer/v2/converter/constant"
"github.com/belphemur/CBZOptimizer/v2/manga"
"github.com/belphemur/CBZOptimizer/v2/utils/errs"
"github.com/spf13/cobra"
"log"
"os"
@@ -17,17 +18,16 @@ import (
// MockConverter is a mock implementation of the Converter interface
type MockConverter struct{}
func (m *MockConverter) Format() constant.ConversionFormat {
return constant.WebP
}
func (m *MockConverter) ConvertChapter(chapter *manga.Chapter, quality uint8, progress func(string)) (*manga.Chapter, error) {
// Simulate conversion by setting the IsConverted flag
func (m *MockConverter) ConvertChapter(chapter *manga.Chapter, quality uint8, split bool, progress func(message string, current uint32, total uint32)) (*manga.Chapter, error) {
chapter.IsConverted = true
chapter.ConvertedTime = time.Now()
return chapter, nil
}
func (m *MockConverter) Format() constant.ConversionFormat {
return constant.WebP
}
func (m *MockConverter) PrepareConverter() error {
return nil
}
@@ -38,7 +38,7 @@ func TestConvertCbzCommand(t *testing.T) {
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tempDir) // Clean up the temp directory when done
defer errs.CaptureGeneric(&err, os.RemoveAll, tempDir, "failed to remove temporary directory")
// Locate the testdata directory
testdataDir := filepath.Join("../testdata")
@@ -79,6 +79,7 @@ func TestConvertCbzCommand(t *testing.T) {
cmd.Flags().Uint8P("quality", "q", 85, "Quality for conversion (0-100)")
cmd.Flags().IntP("parallelism", "n", 2, "Number of chapters to convert in parallel")
cmd.Flags().BoolP("override", "o", false, "Override the original CBZ files")
cmd.Flags().BoolP("split", "s", false, "Split long pages into smaller chunks")
// Execute the command
err = ConvertCbzCommand(cmd, []string{tempDir})

View File

@@ -2,9 +2,9 @@ package cmd
import (
"fmt"
"github.com/belphemur/CBZOptimizer/converter"
"github.com/belphemur/CBZOptimizer/converter/constant"
"github.com/belphemur/CBZOptimizer/utils"
"github.com/belphemur/CBZOptimizer/v2/converter"
"github.com/belphemur/CBZOptimizer/v2/converter/constant"
"github.com/belphemur/CBZOptimizer/v2/utils"
"github.com/pablodz/inotifywaitgo/inotifywaitgo"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -35,6 +35,9 @@ func init() {
command.Flags().BoolP("override", "o", true, "Override the original CBZ files")
_ = viper.BindPFlag("override", command.Flags().Lookup("override"))
command.Flags().BoolP("split", "s", false, "Split long pages into smaller chunks")
_ = viper.BindPFlag("split", command.Flags().Lookup("split"))
command.PersistentFlags().VarP(
formatFlag,
"format", "f",
@@ -61,6 +64,8 @@ func WatchCommand(_ *cobra.Command, args []string) error {
override := viper.GetBool("override")
split := viper.GetBool("split")
converterType := constant.FindConversionFormat(viper.GetString("format"))
chapterConverter, err := converter.Get(converterType)
if err != nil {
@@ -71,7 +76,7 @@ func WatchCommand(_ *cobra.Command, args []string) error {
if err != nil {
return fmt.Errorf("failed to prepare converter: %v", err)
}
log.Printf("Watching [%s] with [override: %t, quality: %d, format: %s]", path, override, quality, converterType.String())
log.Printf("Watching [%s] with [override: %t, quality: %d, format: %s, split: %t]", path, override, quality, converterType.String(), split)
events := make(chan inotifywaitgo.FileEvent)
errors := make(chan error)
@@ -109,7 +114,13 @@ func WatchCommand(_ *cobra.Command, args []string) error {
for _, e := range event.Events {
switch e {
case inotifywaitgo.CLOSE_WRITE, inotifywaitgo.MOVE:
err := utils.Optimize(chapterConverter, event.Filename, quality, override)
err := utils.Optimize(&utils.OptimizeOptions{
ChapterConverter: chapterConverter,
Path: event.Filename,
Quality: quality,
Override: override,
Split: split,
})
if err != nil {
errors <- fmt.Errorf("error processing file %s: %w", event.Filename, err)
}

View File

@@ -2,9 +2,9 @@ package converter
import (
"fmt"
"github.com/belphemur/CBZOptimizer/converter/constant"
"github.com/belphemur/CBZOptimizer/converter/webp"
"github.com/belphemur/CBZOptimizer/manga"
"github.com/belphemur/CBZOptimizer/v2/converter/constant"
"github.com/belphemur/CBZOptimizer/v2/converter/webp"
"github.com/belphemur/CBZOptimizer/v2/manga"
"github.com/samber/lo"
"strings"
)
@@ -12,7 +12,10 @@ import (
type Converter interface {
// Format of the converter
Format() (format constant.ConversionFormat)
ConvertChapter(chapter *manga.Chapter, quality uint8, progress func(string)) (*manga.Chapter, error)
// ConvertChapter converts a manga chapter to the specified format.
//
// Returns partial success where some pages are converted and some are not.
ConvertChapter(chapter *manga.Chapter, quality uint8, split bool, progress func(message string, current uint32, total uint32)) (*manga.Chapter, error)
PrepareConverter() error
}

View File

@@ -2,7 +2,10 @@ package converter
import (
"bytes"
"github.com/belphemur/CBZOptimizer/manga"
"github.com/belphemur/CBZOptimizer/v2/converter/constant"
"github.com/belphemur/CBZOptimizer/v2/manga"
"github.com/belphemur/CBZOptimizer/v2/utils/errs"
"golang.org/x/exp/slices"
"image"
"image/jpeg"
"os"
@@ -14,18 +17,44 @@ func TestConvertChapter(t *testing.T) {
testCases := []struct {
name string
genTestChapter func(path string) (*manga.Chapter, error)
split bool
expectFailure []constant.ConversionFormat
expectPartialSuccess []constant.ConversionFormat
}{
{
name: "All split pages",
genTestChapter: genBigPages,
genTestChapter: genHugePage,
split: true,
expectFailure: []constant.ConversionFormat{},
expectPartialSuccess: []constant.ConversionFormat{},
},
{
name: "Big Pages, no split",
genTestChapter: genHugePage,
split: false,
expectFailure: []constant.ConversionFormat{constant.WebP},
expectPartialSuccess: []constant.ConversionFormat{},
},
{
name: "No split pages",
genTestChapter: genSmallPages,
split: false,
expectFailure: []constant.ConversionFormat{},
expectPartialSuccess: []constant.ConversionFormat{},
},
{
name: "Mix of split and no split pages",
genTestChapter: genMixSmallBig,
split: true,
expectFailure: []constant.ConversionFormat{},
expectPartialSuccess: []constant.ConversionFormat{},
},
{
name: "Mix of Huge and small page",
genTestChapter: genMixSmallHuge,
split: false,
expectFailure: []constant.ConversionFormat{},
expectPartialSuccess: []constant.ConversionFormat{constant.WebP},
},
}
// Load test genTestChapter from testdata
@@ -34,7 +63,7 @@ func TestConvertChapter(t *testing.T) {
t.Fatalf("failed to create temporary file: %v", err)
}
defer os.Remove(temp.Name())
defer errs.CaptureGeneric(&err, os.Remove, temp.Name(), "failed to remove temporary file")
for _, converter := range Available() {
converter, err := Get(converter)
if err != nil {
@@ -50,19 +79,33 @@ func TestConvertChapter(t *testing.T) {
quality := uint8(80)
progress := func(msg string) {
progress := func(msg string, current uint32, total uint32) {
t.Log(msg)
}
convertedChapter, err := converter.ConvertChapter(chapter, quality, progress)
convertedChapter, err := converter.ConvertChapter(chapter, quality, tc.split, progress)
if err != nil {
if convertedChapter != nil && slices.Contains(tc.expectPartialSuccess, converter.Format()) {
t.Logf("Partial success to convert genTestChapter: %v", err)
return
}
if slices.Contains(tc.expectFailure, converter.Format()) {
t.Logf("Expected failure to convert genTestChapter: %v", err)
return
}
t.Fatalf("failed to convert genTestChapter: %v", err)
} else if slices.Contains(tc.expectFailure, converter.Format()) {
t.Fatalf("expected failure to convert genTestChapter didn't happen")
}
if len(convertedChapter.Pages) == 0 {
t.Fatalf("no pages were converted")
}
if len(convertedChapter.Pages) != len(chapter.Pages) {
t.Fatalf("converted chapter has different number of pages")
}
for _, page := range convertedChapter.Pages {
if page.Extension != ".webp" {
t.Errorf("page %d was not converted to webp format", page.Index)
@@ -74,16 +117,16 @@ func TestConvertChapter(t *testing.T) {
}
}
func genBigPages(path string) (*manga.Chapter, error) {
func genHugePage(path string) (*manga.Chapter, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
defer errs.Capture(&err, file.Close, "failed to close file")
var pages []*manga.Page
for i := 0; i < 5; i++ { // Assuming there are 5 pages for the test
img := image.NewRGBA(image.Rect(0, 0, 300, 10000))
for i := 0; i < 1; i++ { // Assuming there are 5 pages for the test
img := image.NewRGBA(image.Rect(0, 0, 1, 17000))
buf := new(bytes.Buffer)
err := jpeg.Encode(buf, img, nil)
if err != nil {
@@ -108,7 +151,7 @@ func genSmallPages(path string) (*manga.Chapter, error) {
if err != nil {
return nil, err
}
defer file.Close()
defer errs.Capture(&err, file.Close, "failed to close file")
var pages []*manga.Page
for i := 0; i < 5; i++ { // Assuming there are 5 pages for the test
@@ -137,7 +180,7 @@ func genMixSmallBig(path string) (*manga.Chapter, error) {
if err != nil {
return nil, err
}
defer file.Close()
defer errs.Capture(&err, file.Close, "failed to close file")
var pages []*manga.Page
for i := 0; i < 5; i++ { // Assuming there are 5 pages for the test
@@ -160,3 +203,32 @@ func genMixSmallBig(path string) (*manga.Chapter, error) {
Pages: pages,
}, nil
}
func genMixSmallHuge(path string) (*manga.Chapter, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer errs.Capture(&err, file.Close, "failed to close file")
var pages []*manga.Page
for i := 0; i < 10; i++ { // Assuming there are 5 pages for the test
img := image.NewRGBA(image.Rect(0, 0, 1, 2000*(i+1)))
buf := new(bytes.Buffer)
err := jpeg.Encode(buf, img, nil)
if err != nil {
return nil, err
}
page := &manga.Page{
Index: uint16(i),
Contents: buf,
Extension: ".jpg",
}
pages = append(pages, page)
}
return &manga.Chapter{
FilePath: path,
Pages: pages,
}, nil
}

View File

@@ -0,0 +1,13 @@
package errors
type PageIgnoredError struct {
s string
}
func (e *PageIgnoredError) Error() string {
return e.s
}
func NewPageIgnored(text string) error {
return &PageIgnoredError{text}
}

View File

@@ -2,9 +2,11 @@ package webp
import (
"bytes"
"errors"
"fmt"
"github.com/belphemur/CBZOptimizer/converter/constant"
packer2 "github.com/belphemur/CBZOptimizer/manga"
"github.com/belphemur/CBZOptimizer/v2/converter/constant"
converterrors "github.com/belphemur/CBZOptimizer/v2/converter/errors"
"github.com/belphemur/CBZOptimizer/v2/manga"
"github.com/oliamb/cutter"
"golang.org/x/exp/slices"
_ "golang.org/x/image/webp"
@@ -17,6 +19,8 @@ import (
"sync/atomic"
)
const webpMaxHeight = 16383
type Converter struct {
maxHeight int
cropHeight int
@@ -48,7 +52,7 @@ func (converter *Converter) PrepareConverter() error {
return nil
}
func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uint8, progress func(string)) (*packer2.Chapter, error) {
func (converter *Converter) ConvertChapter(chapter *manga.Chapter, quality uint8, split bool, progress func(message string, current uint32, total uint32)) (*manga.Chapter, error) {
err := converter.PrepareConverter()
if err != nil {
return nil, err
@@ -57,7 +61,7 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin
var wgConvertedPages sync.WaitGroup
maxGoroutines := runtime.NumCPU()
pagesChan := make(chan *packer2.PageContainer, maxGoroutines)
pagesChan := make(chan *manga.PageContainer, maxGoroutines)
errChan := make(chan error, maxGoroutines)
var wgPages sync.WaitGroup
@@ -65,13 +69,13 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin
guard := make(chan struct{}, maxGoroutines)
pagesMutex := sync.Mutex{}
var pages []*packer2.Page
var pages []*manga.Page
var totalPages = uint32(len(chapter.Pages))
go func() {
for page := range pagesChan {
guard <- struct{}{} // would block if guard channel is already filled
go func(pageToConvert *packer2.PageContainer) {
go func(pageToConvert *manga.PageContainer) {
defer wgConvertedPages.Done()
convertedPage, err := converter.convertPage(pageToConvert, quality)
if err != nil {
@@ -93,7 +97,7 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin
}
pagesMutex.Lock()
pages = append(pages, convertedPage.Page)
progress(fmt.Sprintf("Converted %d/%d pages to %s format", len(pages), totalPages, converter.Format()))
progress(fmt.Sprintf("Converted %d/%d pages to %s format", len(pages), totalPages, converter.Format()), uint32(len(pages)), totalPages)
pagesMutex.Unlock()
<-guard
}(page)
@@ -101,31 +105,36 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin
}()
for _, page := range chapter.Pages {
go func(page *packer2.Page) {
go func(page *manga.Page) {
defer wgPages.Done()
splitNeeded, img, format, err := converter.checkPageNeedsSplit(page)
splitNeeded, img, format, err := converter.checkPageNeedsSplit(page, split)
if err != nil {
errChan <- fmt.Errorf("error checking if page %d of genTestChapter %s needs split: %v", page.Index, chapter.FilePath, err)
errChan <- err
// Partial error in this case, we want the page, but not converting it
if img != nil {
wgConvertedPages.Add(1)
pagesChan <- manga.NewContainer(page, img, format, false)
}
return
}
if !splitNeeded {
wgConvertedPages.Add(1)
pagesChan <- packer2.NewContainer(page, img, format)
pagesChan <- manga.NewContainer(page, img, format, true)
return
}
images, err := converter.cropImage(img)
if err != nil {
errChan <- fmt.Errorf("error converting page %d of genTestChapter %s to webp: %v", page.Index, chapter.FilePath, err)
errChan <- err
return
}
atomic.AddUint32(&totalPages, uint32(len(images)-1))
for i, img := range images {
page := &packer2.Page{Index: page.Index, IsSplitted: true, SplitPartIndex: uint16(i)}
page := &manga.Page{Index: page.Index, IsSplitted: true, SplitPartIndex: uint16(i)}
wgConvertedPages.Add(1)
pagesChan <- packer2.NewContainer(page, img, "N/A")
pagesChan <- manga.NewContainer(page, img, "N/A", true)
}
}(page)
}
@@ -140,11 +149,12 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin
errList = append(errList, err)
}
var aggregatedError error = nil
if len(errList) > 0 {
return nil, fmt.Errorf("encountered errors: %v", errList)
aggregatedError = errors.Join(errList...)
}
slices.SortFunc(pages, func(a, b *packer2.Page) int {
slices.SortFunc(pages, func(a, b *manga.Page) int {
if a.Index == b.Index {
return int(b.SplitPartIndex - a.SplitPartIndex)
}
@@ -154,7 +164,7 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin
runtime.GC()
return chapter, nil
return chapter, aggregatedError
}
func (converter *Converter) cropImage(img image.Image) ([]image.Image, error) {
@@ -190,7 +200,7 @@ func (converter *Converter) cropImage(img image.Image) ([]image.Image, error) {
return parts, nil
}
func (converter *Converter) checkPageNeedsSplit(page *packer2.Page) (bool, image.Image, string, error) {
func (converter *Converter) checkPageNeedsSplit(page *manga.Page, splitRequested bool) (bool, image.Image, string, error) {
reader := io.Reader(bytes.NewBuffer(page.Contents.Bytes()))
img, format, err := image.Decode(reader)
if err != nil {
@@ -200,13 +210,19 @@ func (converter *Converter) checkPageNeedsSplit(page *packer2.Page) (bool, image
bounds := img.Bounds()
height := bounds.Dy()
return height >= converter.maxHeight, img, format, nil
if height >= webpMaxHeight && !splitRequested {
return false, img, format, converterrors.NewPageIgnored(fmt.Sprintf("page %d is too tall [max: %dpx] to be converted to webp format", page.Index, webpMaxHeight))
}
return height >= converter.maxHeight && splitRequested, img, format, nil
}
func (converter *Converter) convertPage(container *packer2.PageContainer, quality uint8) (*packer2.PageContainer, error) {
func (converter *Converter) convertPage(container *manga.PageContainer, quality uint8) (*manga.PageContainer, error) {
if container.Format == "webp" {
return container, nil
}
if !container.IsToBeConverted {
return container, nil
}
converted, err := converter.convert(container.Image, uint(quality))
if err != nil {
return nil, err

View File

@@ -1,22 +1,19 @@
package webp
import (
"github.com/belphemur/go-webpbin/v2"
"github.com/kolesa-team/go-webp/encoder"
"github.com/kolesa-team/go-webp/webp"
"image"
"io"
)
const libwebpVersion = "1.4.0"
func PrepareEncoder() error {
webpbin.SetLibVersion(libwebpVersion)
container := webpbin.NewCWebP()
return container.BinWrapper.Run()
return nil
}
func Encode(w io.Writer, m image.Image, quality uint) error {
return webpbin.NewCWebP().
Quality(quality).
InputImage(m).
Output(w).
Run()
options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, float32(quality))
if err != nil {
return err
}
return webp.Encode(w, m, options)
}

34
go.mod
View File

@@ -1,36 +1,30 @@
module github.com/belphemur/CBZOptimizer
module github.com/belphemur/CBZOptimizer/v2
go 1.23.0
go 1.23.1
toolchain go1.23.5
require (
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/belphemur/go-webpbin/v2 v2.0.0
github.com/kolesa-team/go-webp v1.0.4
github.com/oliamb/cutter v0.2.2
github.com/pablodz/inotifywaitgo v0.0.7
github.com/samber/lo v1.47.0
github.com/pablodz/inotifywaitgo v0.0.9
github.com/samber/lo v1.49.0
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/thediveo/enumflag/v2 v2.0.5
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
golang.org/x/image v0.19.0
github.com/thediveo/enumflag/v2 v2.0.7
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
golang.org/x/image v0.23.0
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/belphemur/go-binwrapper v0.0.0-20240827152605-33977349b1f0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jfrog/archiver/v3 v3.6.1 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
@@ -38,12 +32,10 @@ require (
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

75
go.sum
View File

@@ -1,19 +1,10 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
github.com/belphemur/go-binwrapper v0.0.0-20240827152605-33977349b1f0 h1:EzKgPYK90TyAOmytK7bvapqlkG/m7KWKK28mOAdQEaM=
github.com/belphemur/go-binwrapper v0.0.0-20240827152605-33977349b1f0/go.mod h1:s2Dv+CfgVbNM9ucqvE5qCCC0AkI1PE2OZb7N8PPlOh4=
github.com/belphemur/go-webpbin/v2 v2.0.0 h1:Do0TTTJ6cS6lgi+R67De+jXRYe+ZOwxFqTiFggyX5p8=
github.com/belphemur/go-webpbin/v2 v2.0.0/go.mod h1:VIHXZQaIwaIYDn08w0qeJFPj1MuYt5pyJnkQALPYc5g=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@@ -22,9 +13,6 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
@@ -33,14 +21,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jfrog/archiver/v3 v3.6.1 h1:LOxnkw9pOn45DzCbZNFV6K0+6dCsQ0L8mR3ZcujO5eI=
github.com/jfrog/archiver/v3 v3.6.1/go.mod h1:VgR+3WZS4N+i9FaDwLZbq+jeU4B4zctXL+gL4EMzfLw=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kolesa-team/go-webp v1.0.4 h1:wQvU4PLG/X7RS0vAeyhiivhLRoxfLVRlDq4I3frdxIQ=
github.com/kolesa-team/go-webp v1.0.4/go.mod h1:oMvdivD6K+Q5qIIkVC2w4k2ZUnI1H+MyP7inwgWq9aA=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -50,8 +32,6 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/oliamb/cutter v0.2.2 h1:Lfwkya0HHNU1YLnGv2hTkzHfasrSMkgv4Dn+5rmlk3k=
github.com/oliamb/cutter v0.2.2/go.mod h1:4BenG2/4GuRBDbVm/OPahDVqbrOemzpPiG5mi1iryBU=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
@@ -60,10 +40,10 @@ github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA=
github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/pablodz/inotifywaitgo v0.0.7 h1:1ii49dGBnRn0t1Sz7RGZS6/NberPEDQprwKHN49Bv6U=
github.com/pablodz/inotifywaitgo v0.0.7/go.mod h1:OtzRCsYTJlIr+vAzlOtauTkfQ1c25ebFuXq8tbbf8cw=
github.com/pablodz/inotifywaitgo v0.0.9 h1:njquRbBU7fuwIe5rEvtaniVBjwWzcpdUVptSgzFqZsw=
github.com/pablodz/inotifywaitgo v0.0.9/go.mod h1:hAfx2oN+WKg8miwUKPs52trySpPignlRBRxWcXVHku0=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -77,6 +57,10 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/samber/lo v1.48.0 h1:ELOfcaM7vdYPe0egBS2Nxa8LxkY4lR+9LBzj0l6cHJ0=
github.com/samber/lo v1.48.0/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/samber/lo v1.49.0 h1:AGnTnQrg1jpFuwECPUSoxZCfVH5W22b605kWSry3YxM=
github.com/samber/lo v1.49.0/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -107,30 +91,45 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/thediveo/enumflag/v2 v2.0.5 h1:VJjvlAqUb6m6mxOrB/0tfBJI0Kvi9wJ8ulh38xK87i8=
github.com/thediveo/enumflag/v2 v2.0.5/go.mod h1:0NcG67nYgwwFsAvoQCmezG0J0KaIxZ0f7skg9eLq1DA=
github.com/thediveo/enumflag/v2 v2.0.7 h1:uxXDU+rTel7Hg4X0xdqICpG9rzuI/mzLAEYXWLflOfs=
github.com/thediveo/enumflag/v2 v2.0.7/go.mod h1:bWlnNvTJuUK+huyzf3WECFLy557Ttlc+yk3o+BPs0EA=
github.com/thediveo/success v1.0.1 h1:NVwUOwKUwaN8szjkJ+vsiM2L3sNBFscldoDJ2g2tAPg=
github.com/thediveo/success v1.0.1/go.mod h1:AZ8oUArgbIsCuDEWrzWNQHdKnPbDOLQsWOFj9ynwLt0=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20241210172134-14434422244c h1:G0f8LmhCW7rzpybldgSjhhKDCwW7mYO0Qr6HZDb0HJA=
golang.org/x/exp v0.0.0-20241210172134-14434422244c/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4=
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20250103163809-dd03c70a0a45 h1:tHbTCyM7JD+jm5mShcnp+cl1Q6nJpFZe7kyALJCclHA=
golang.org/x/exp v0.0.0-20250103163809-dd03c70a0a45/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588=
golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -1,7 +1,7 @@
package main
import (
"github.com/belphemur/CBZOptimizer/cmd"
"github.com/belphemur/CBZOptimizer/v2/cmd"
)
var (

View File

@@ -10,8 +10,10 @@ type PageContainer struct {
Image image.Image
// Format is a string representing the format of the image (e.g., "png", "jpeg", "webp").
Format string
// IsToBeConverted is a boolean flag indicating whether the image needs to be converted to another format.
IsToBeConverted bool
}
func NewContainer(Page *Page, img image.Image, format string) *PageContainer {
return &PageContainer{Page: Page, Image: img, Format: format}
func NewContainer(Page *Page, img image.Image, format string, isToBeConverted bool) *PageContainer {
return &PageContainer{Page: Page, Image: img, Format: format, IsToBeConverted: isToBeConverted}
}

View File

@@ -1,5 +0,0 @@
package meta
var Version = "v0.0.0"
var Commit = ""
var Date = ""

View File

@@ -2,5 +2,16 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"packageRules": [
{
"matchUpdateTypes": [
"minor",
"patch",
"digest"
],
"matchCurrentVersion": "!/^0/",
"automerge": true
}
]
}

View File

@@ -0,0 +1,25 @@
package errs
import (
"errors"
"fmt"
)
// Capture runs errFunc and assigns the error, if any, to *errPtr. Preserves the
// original error by wrapping with errors.Join if the errFunc err is non-nil.
func Capture(errPtr *error, errFunc func() error, msg string) {
err := errFunc()
if err == nil {
return
}
*errPtr = errors.Join(*errPtr, fmt.Errorf("%s: %w", msg, err))
}
// CaptureGeneric runs errFunc with a generic type K and assigns the error, if any, to *errPtr.
func CaptureGeneric[K any](errPtr *error, errFunc func(value K) error, value K, msg string) {
err := errFunc(value)
if err == nil {
return
}
*errPtr = errors.Join(*errPtr, fmt.Errorf("%s: %w", msg, err))
}

View File

@@ -0,0 +1,122 @@
package errs
import (
"errors"
"fmt"
"testing"
)
func TestCapture(t *testing.T) {
tests := []struct {
name string
initial error
errFunc func() error
msg string
expected string
}{
{
name: "No error from errFunc",
initial: nil,
errFunc: func() error { return nil },
msg: "test message",
expected: "",
},
{
name: "Error from errFunc with no initial error",
initial: nil,
errFunc: func() error { return errors.New("error from func") },
msg: "test message",
expected: "test message: error from func",
},
{
name: "Error from errFunc with initial error",
initial: errors.New("initial error"),
errFunc: func() error { return errors.New("error from func") },
msg: "test message",
expected: "initial error\ntest message: error from func",
},
{
name: "Error from errFunc with initial wrapped error",
initial: fmt.Errorf("wrapped error: %w", errors.New("initial error")),
errFunc: func() error { return errors.New("error from func") },
msg: "test message",
expected: "wrapped error: initial error\ntest message: error from func",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var err error = tt.initial
Capture(&err, tt.errFunc, tt.msg)
if err != nil && err.Error() != tt.expected {
t.Errorf("expected %q, got %q", tt.expected, err.Error())
} else if err == nil && tt.expected != "" {
t.Errorf("expected %q, got nil", tt.expected)
}
})
}
}
func TestCaptureGeneric(t *testing.T) {
tests := []struct {
name string
initial error
errFunc func(int) error
value int
msg string
expected string
}{
{
name: "No error from errFunc",
initial: nil,
errFunc: func(value int) error { return nil },
value: 0,
msg: "test message",
expected: "",
},
{
name: "Error from errFunc with no initial error",
initial: nil,
errFunc: func(value int) error { return errors.New("error from func") },
value: 0,
msg: "test message",
expected: "test message: error from func",
},
{
name: "Error from errFunc with initial error",
initial: errors.New("initial error"),
errFunc: func(value int) error { return errors.New("error from func") },
value: 0,
msg: "test message",
expected: "initial error\ntest message: error from func",
},
{
name: "Error from errFunc with initial wrapped error",
initial: fmt.Errorf("wrapped error: %w", errors.New("initial error")),
errFunc: func(value int) error { return errors.New("error from func") },
value: 0,
msg: "test message",
expected: "wrapped error: initial error\ntest message: error from func",
},
{
name: "Error from errFunc with initial wrapped error and value",
initial: fmt.Errorf("wrapped error: %w", errors.New("initial error")),
errFunc: func(value int) error { return fmt.Errorf("hello error:%d", value) },
value: 1,
msg: "test message",
expected: "wrapped error: initial error\ntest message: hello error:1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var err error = tt.initial
CaptureGeneric(&err, tt.errFunc, tt.value, tt.msg)
if err != nil && err.Error() != tt.expected {
t.Errorf("expected %q, got %q", tt.expected, err.Error())
} else if err == nil && tt.expected != "" {
t.Errorf("expected %q, got nil", tt.expected)
}
})
}
}

View File

@@ -1,41 +1,60 @@
package utils
import (
"errors"
"fmt"
"github.com/belphemur/CBZOptimizer/cbz"
"github.com/belphemur/CBZOptimizer/converter"
"github.com/belphemur/CBZOptimizer/v2/cbz"
"github.com/belphemur/CBZOptimizer/v2/converter"
errors2 "github.com/belphemur/CBZOptimizer/v2/converter/errors"
"log"
"strings"
)
type OptimizeOptions struct {
ChapterConverter converter.Converter
Path string
Quality uint8
Override bool
Split bool
}
// Optimize optimizes a CBZ file using the specified converter.
func Optimize(chapterConverter converter.Converter, path string, quality uint8, override bool) error {
log.Printf("Processing file: %s\n", path)
func Optimize(options *OptimizeOptions) error {
log.Printf("Processing file: %s\n", options.Path)
// Load the chapter
chapter, err := cbz.LoadChapter(path)
chapter, err := cbz.LoadChapter(options.Path)
if err != nil {
return fmt.Errorf("failed to load chapter: %v", err)
}
if chapter.IsConverted {
log.Printf("Chapter already converted: %s", path)
log.Printf("Chapter already converted: %s", options.Path)
return nil
}
// Convert the chapter
convertedChapter, err := chapterConverter.ConvertChapter(chapter, quality, func(msg string) {
log.Printf("[%s]%s", path, msg)
convertedChapter, err := options.ChapterConverter.ConvertChapter(chapter, options.Quality, options.Split, func(msg string, current uint32, total uint32) {
if current%10 == 0 || current == total {
log.Printf("[%s] Converting: %d/%d", chapter.FilePath, current, total)
}
})
if err != nil {
var pageIgnoredError *errors2.PageIgnoredError
if !errors.As(err, &pageIgnoredError) {
return fmt.Errorf("failed to convert chapter: %v", err)
}
}
if convertedChapter == nil {
return fmt.Errorf("failed to convert chapter")
}
convertedChapter.SetConverted()
// Write the converted chapter back to a CBZ file
outputPath := path
if !override {
outputPath = strings.TrimSuffix(path, ".cbz") + "_converted.cbz"
outputPath := options.Path
if !options.Override {
outputPath = strings.TrimSuffix(options.Path, ".cbz") + "_converted.cbz"
}
err = cbz.WriteChapterToCBZ(convertedChapter, outputPath)
if err != nil {