go-comic-converter/internal/epub/image_processing.go
2022-12-30 14:10:19 +01:00

315 lines
5.5 KiB
Go

package epub
import (
"archive/zip"
"bytes"
"fmt"
imageconverter "go-comic-converter/internal/image-converter"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"github.com/nwaples/rardecode"
pdfimage "github.com/raff/pdfreader/image"
"github.com/raff/pdfreader/pdfread"
"golang.org/x/image/tiff"
)
type Image struct {
Id int
Data *ImageData
Width int
Height int
}
type imageTask struct {
Id int
Reader io.ReadCloser
}
func LoadImages(path string, options *ImageOptions) ([]*Image, error) {
images := make([]*Image, 0)
fi, err := os.Stat(path)
if err != nil {
return nil, err
}
var (
imageCount int
imageInput chan *imageTask
)
if fi.IsDir() {
imageCount, imageInput, err = loadDir(path)
} else {
switch ext := strings.ToLower(filepath.Ext(path)); ext {
case ".cbz", "zip":
imageCount, imageInput, err = loadCbz(path)
case ".cbr", "rar":
imageCount, imageInput, err = loadCbr(path)
case ".pdf":
imageCount, imageInput, err = loadPdf(path)
default:
err = fmt.Errorf("unknown file format (%s): support .cbz, .cbr, .pdf", ext)
}
}
if err != nil {
return nil, err
}
imageOutput := make(chan *Image)
// processing
wg := &sync.WaitGroup{}
bar := NewBar(imageCount, "Processing", 1, 2)
for i := 0; i < runtime.NumCPU(); i++ {
wg.Add(1)
go func() {
defer wg.Done()
for img := range imageInput {
data, w, h := imageconverter.Convert(
img.Reader,
options.Crop,
options.ViewWidth,
options.ViewHeight,
options.Quality,
)
name := fmt.Sprintf("OEBPS/Images/%d.jpg", img.Id)
if img.Id == 0 {
name = "OEBPS/Images/cover.jpg"
}
imageOutput <- &Image{
img.Id,
newImageData(name, data),
w,
h,
}
}
}()
}
go func() {
wg.Wait()
bar.Close()
close(imageOutput)
}()
for image := range imageOutput {
images = append(images, image)
bar.Add(1)
}
if len(images) == 0 {
return nil, fmt.Errorf("image not found")
}
sort.Slice(images, func(i, j int) bool {
return images[i].Id < images[j].Id
})
return images, nil
}
func loadDir(input string) (int, chan *imageTask, error) {
images := make([]string, 0)
err := filepath.WalkDir(input, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
ext := filepath.Ext(path)
if strings.ToLower(ext) != ".jpg" {
return nil
}
images = append(images, path)
return nil
})
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if len(images) == 0 {
return 0, nil, fmt.Errorf("image not found")
}
sort.Strings(images)
output := make(chan *imageTask)
go func() {
defer close(output)
for i, img := range images {
f, err := os.Open(img)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
output <- &imageTask{
Id: i,
Reader: f,
}
}
}()
return len(images), output, nil
}
func loadCbz(input string) (int, chan *imageTask, error) {
r, err := zip.OpenReader(input)
if err != nil {
return 0, nil, err
}
images := make([]*zip.File, 0)
for _, f := range r.File {
if f.FileInfo().IsDir() {
continue
}
if strings.ToLower(filepath.Ext(f.Name)) != ".jpg" {
continue
}
images = append(images, f)
}
if len(images) == 0 {
r.Close()
return 0, nil, fmt.Errorf("no images found")
}
sort.SliceStable(images, func(i, j int) bool {
return strings.Compare(images[i].Name, images[j].Name) < 0
})
output := make(chan *imageTask)
go func() {
defer close(output)
for i, img := range images {
f, err := img.Open()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
output <- &imageTask{
Id: i,
Reader: f,
}
}
}()
return len(images), output, nil
}
func loadCbr(input string) (int, chan *imageTask, error) {
// listing and indexing
rl, err := rardecode.OpenReader(input, "")
if err != nil {
return 0, nil, err
}
names := make([]string, 0)
for {
f, err := rl.Next()
if err != nil && err != io.EOF {
rl.Close()
return 0, nil, err
}
if f == nil {
break
}
if f.IsDir {
continue
}
if strings.ToLower(filepath.Ext(f.Name)) != ".jpg" {
continue
}
names = append(names, f.Name)
}
rl.Close()
if len(names) == 0 {
return 0, nil, fmt.Errorf("no images found")
}
sort.Strings(names)
indexedNames := make(map[string]int)
for i, name := range names {
indexedNames[name] = i
}
// send file to the queue
output := make(chan *imageTask)
go func() {
defer close(output)
r, err := rardecode.OpenReader(input, "")
if err != nil {
panic(err)
}
defer r.Close()
for {
f, err := r.Next()
if err != nil && err != io.EOF {
panic(err)
}
if f == nil {
break
}
if idx, ok := indexedNames[f.Name]; ok {
b := bytes.NewBuffer([]byte{})
io.Copy(b, r)
output <- &imageTask{
Id: idx,
Reader: io.NopCloser(b),
}
}
}
}()
return len(names), output, nil
}
func loadPdf(input string) (int, chan *imageTask, error) {
pdf := pdfread.Load(input)
if pdf == nil {
return 0, nil, fmt.Errorf("can't read pdf")
}
nbPages := len(pdf.Pages())
output := make(chan *imageTask)
go func() {
defer close(output)
defer pdf.Close()
for i := 0; i < nbPages; i++ {
img, err := pdfimage.Extract(pdf, i+1)
if err != nil {
panic(err)
}
b := bytes.NewBuffer([]byte{})
err = tiff.Encode(b, img, nil)
if err != nil {
panic(err)
}
output <- &imageTask{
Id: i,
Reader: io.NopCloser(b),
}
}
}()
return nbPages, output, nil
}