fix(chapter): fix chapter conversion.

Still need to figure out the memory issues

Consolidates image conversion logic into a dedicated method.

This change streamlines the conversion process by centralizing the
setting of converted image data, extension, and size. It also
introduces a flag to track whether an image has been converted.

The old resource cleanup has been removed since it is not needed anymore.
This commit is contained in:
Antoine Aflalo
2025-02-14 10:03:35 -05:00
parent 4d3391273c
commit 23eb43c691
5 changed files with 125 additions and 31 deletions

View File

@@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="GoDfaErrorMayBeNotNil" enabled="true" level="WARNING" enabled_by_default="true">
<methods>
<method importPath="github.com/belphemur/CBZOptimizer/converter" receiver="Converter" name="ConvertChapter" />
</methods>
</inspection_tool>
</profile>
</component>

16
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}"
}
]
}

View File

@@ -1,6 +1,9 @@
package manga package manga
import "image" import (
"bytes"
"image"
)
// PageContainer is a struct that holds a manga page, its image, and the image format. // PageContainer is a struct that holds a manga page, its image, and the image format.
type PageContainer struct { type PageContainer struct {
@@ -12,16 +15,18 @@ type PageContainer struct {
Format string Format string
// IsToBeConverted is a boolean flag indicating whether the image needs to be converted to another format. // IsToBeConverted is a boolean flag indicating whether the image needs to be converted to another format.
IsToBeConverted bool IsToBeConverted bool
// HasBeenConverted is a boolean flag indicating whether the image has been converted to another format.
HasBeenConverted bool
} }
func NewContainer(Page *Page, img image.Image, format string, isToBeConverted bool) *PageContainer { func NewContainer(Page *Page, img image.Image, format string, isToBeConverted bool) *PageContainer {
return &PageContainer{Page: Page, Image: img, Format: format, IsToBeConverted: isToBeConverted} return &PageContainer{Page: Page, Image: img, Format: format, IsToBeConverted: isToBeConverted, HasBeenConverted: false}
} }
// Close releases resources held by the PageContainer // SetConverted sets the converted image, its extension, and its size in the PageContainer.
func (pc *PageContainer) Close() { func (pc *PageContainer) SetConverted(converted *bytes.Buffer, extension string) {
pc.Image = nil pc.Page.Contents = converted
if pc.Page != nil && pc.Page.Contents != nil { pc.Page.Extension = extension
pc.Page.Contents.Reset() pc.Page.Size = uint64(converted.Len())
} pc.HasBeenConverted = true
} }

View File

@@ -80,7 +80,6 @@ func (converter *Converter) ConvertChapter(chapter *manga.Chapter, quality uint8
go func(pageToConvert *manga.PageContainer) { go func(pageToConvert *manga.PageContainer) {
defer func() { defer func() {
wgConvertedPages.Done() wgConvertedPages.Done()
pageToConvert.Close() // Clean up resources
<-guard <-guard
}() }()
@@ -243,9 +242,7 @@ func (converter *Converter) convertPage(container *manga.PageContainer, quality
if err != nil { if err != nil {
return nil, err return nil, err
} }
container.Page.Contents = converted container.SetConverted(converted, ".webp");
container.Page.Extension = ".webp"
container.Page.Size = uint64(converted.Len())
return container, nil return container, nil
} }

View File

@@ -4,37 +4,96 @@ import (
"bytes" "bytes"
"image" "image"
"image/color" "image/color"
"image/draw" "image/jpeg"
"image/png" "image/png"
"sync" "sync"
"testing" "testing"
_ "image/jpeg"
_ "golang.org/x/image/webp"
"github.com/belphemur/CBZOptimizer/v2/internal/manga" "github.com/belphemur/CBZOptimizer/v2/internal/manga"
"github.com/belphemur/CBZOptimizer/v2/pkg/converter/constant" "github.com/belphemur/CBZOptimizer/v2/pkg/converter/constant"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func createTestImage(width, height int) image.Image { func createTestImage(width, height int, format string) (image.Image, error) {
img := image.NewRGBA(image.Rect(0, 0, width, height)) img := image.NewRGBA(image.Rect(0, 0, width, height))
draw.Draw(img, img.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)
return img // Create a gradient pattern to ensure we have actual image data
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
img.Set(x, y, color.RGBA{
R: uint8((x * 255) / width),
G: uint8((y * 255) / height),
B: 100,
A: 255,
})
}
}
return img, nil
} }
func createTestPage(t *testing.T, index int, width, height int) *manga.Page { func encodeImage(img image.Image, format string) (*bytes.Buffer, string, error) {
img := createTestImage(width, height) buf := new(bytes.Buffer)
var buf bytes.Buffer
err := png.Encode(&buf, img) switch format {
case "jpeg", "jpg":
if err := jpeg.Encode(buf, img, &jpeg.Options{Quality: 85}); err != nil {
return nil, "", err
}
return buf, ".jpg", nil
case "webp":
PrepareEncoder()
if err := Encode(buf, img, 80); err != nil {
return nil, "", err
}
return buf, ".webp", nil
case "png":
fallthrough
default:
if err := png.Encode(buf, img); err != nil {
return nil, "", err
}
return buf, ".png", nil
}
}
func createTestPage(t *testing.T, index int, width, height int, format string) *manga.Page {
img, err := createTestImage(width, height, format)
require.NoError(t, err)
buf, ext, err := encodeImage(img, format)
require.NoError(t, err) require.NoError(t, err)
return &manga.Page{ return &manga.Page{
Index: uint16(index), Index: uint16(index),
Contents: &buf, Contents: buf,
Extension: ".png", Extension: ext,
Size: uint64(buf.Len()), Size: uint64(buf.Len()),
} }
} }
func validateConvertedImage(t *testing.T, page *manga.Page) {
require.NotNil(t, page.Contents)
require.Greater(t, page.Contents.Len(), 0)
// Try to decode the image
img, format, err := image.Decode(bytes.NewReader(page.Contents.Bytes()))
require.NoError(t, err, "Failed to decode converted image")
if page.Extension == ".webp" {
assert.Equal(t, "webp", format, "Expected WebP format")
}
require.NotNil(t, img)
bounds := img.Bounds()
assert.Greater(t, bounds.Dx(), 0, "Image width should be positive")
assert.Greater(t, bounds.Dy(), 0, "Image height should be positive")
}
// TestConverter_ConvertChapter tests the ConvertChapter method of the WebP converter. // TestConverter_ConvertChapter tests the ConvertChapter method of the WebP converter.
// It verifies various scenarios including: // It verifies various scenarios including:
// - Converting single normal images // - Converting single normal images
@@ -63,7 +122,7 @@ func TestConverter_ConvertChapter(t *testing.T) {
}{ }{
{ {
name: "Single normal image", name: "Single normal image",
pages: []*manga.Page{createTestPage(t, 1, 800, 1200)}, pages: []*manga.Page{createTestPage(t, 1, 800, 1200, "jpeg")},
split: false, split: false,
expectSplit: false, expectSplit: false,
numExpected: 1, numExpected: 1,
@@ -71,8 +130,8 @@ func TestConverter_ConvertChapter(t *testing.T) {
{ {
name: "Multiple normal images", name: "Multiple normal images",
pages: []*manga.Page{ pages: []*manga.Page{
createTestPage(t, 1, 800, 1200), createTestPage(t, 1, 800, 1200, "png"),
createTestPage(t, 2, 800, 1200), createTestPage(t, 2, 800, 1200, "jpeg"),
}, },
split: false, split: false,
expectSplit: false, expectSplit: false,
@@ -80,14 +139,14 @@ func TestConverter_ConvertChapter(t *testing.T) {
}, },
{ {
name: "Tall image with split enabled", name: "Tall image with split enabled",
pages: []*manga.Page{createTestPage(t, 1, 800, 5000)}, pages: []*manga.Page{createTestPage(t, 1, 800, 5000, "jpeg")},
split: true, split: true,
expectSplit: true, expectSplit: true,
numExpected: 3, // Based on cropHeight of 2000 numExpected: 3, // Based on cropHeight of 2000
}, },
{ {
name: "Tall image without split", name: "Tall image without split",
pages: []*manga.Page{createTestPage(t, 1, 800, webpMaxHeight+100)}, pages: []*manga.Page{createTestPage(t, 1, 800, webpMaxHeight+100, "png")},
split: false, split: false,
expectError: true, expectError: true,
numExpected: 1, numExpected: 1,
@@ -128,6 +187,11 @@ func TestConverter_ConvertChapter(t *testing.T) {
require.NotNil(t, convertedChapter) require.NotNil(t, convertedChapter)
assert.Len(t, convertedChapter.Pages, tt.numExpected) assert.Len(t, convertedChapter.Pages, tt.numExpected)
// Validate all converted images
for _, page := range convertedChapter.Pages {
validateConvertedImage(t, page)
}
// Verify page order // Verify page order
for i := 1; i < len(convertedChapter.Pages); i++ { for i := 1; i < len(convertedChapter.Pages); i++ {
prevPage := convertedChapter.Pages[i-1] prevPage := convertedChapter.Pages[i-1]
@@ -189,9 +253,10 @@ func TestConverter_convertPage(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
page := createTestPage(t, 1, 100, 100) page := createTestPage(t, 1, 100, 100, tt.format)
container := manga.NewContainer(page, createTestImage(100, 100), tt.format, tt.isToBeConverted) img, err := createTestImage(100, 100, tt.format)
defer container.Close() require.NoError(t, err)
container := manga.NewContainer(page, img, tt.format, tt.isToBeConverted)
converted, err := converter.convertPage(container, 80) converted, err := converter.convertPage(container, 80)
require.NoError(t, err) require.NoError(t, err)
@@ -199,6 +264,7 @@ func TestConverter_convertPage(t *testing.T) {
if tt.expectWebP { if tt.expectWebP {
assert.Equal(t, ".webp", converted.Page.Extension) assert.Equal(t, ".webp", converted.Page.Extension)
validateConvertedImage(t, converted.Page)
} else { } else {
assert.NotEqual(t, ".webp", converted.Page.Extension) assert.NotEqual(t, ".webp", converted.Page.Extension)
} }
@@ -238,7 +304,7 @@ func TestConverter_checkPageNeedsSplit(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
page := createTestPage(t, 1, 800, tt.imageHeight) page := createTestPage(t, 1, 800, tt.imageHeight, "jpeg")
needsSplit, img, format, err := converter.checkPageNeedsSplit(page, tt.split) needsSplit, img, format, err := converter.checkPageNeedsSplit(page, tt.split)