decode image into loader

This commit is contained in:
Celogeek 2023-04-27 12:02:47 +02:00
parent 721d373a7f
commit 4553e1e673
Signed by: celogeek
SSH Key Fingerprint: SHA256:njNJLzoLQdbV9PC6ehcruRb0QnEgxABoCYZ+0+aUIYc
4 changed files with 204 additions and 130 deletions

2
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/disintegration/gift v1.2.1 github.com/disintegration/gift v1.2.1
github.com/gofrs/uuid v4.4.0+incompatible github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/nwaples/rardecode v1.1.3 github.com/nwaples/rardecode/v2 v2.0.0-beta.2
github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0 github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0
github.com/schollz/progressbar/v3 v3.13.1 github.com/schollz/progressbar/v3 v3.13.1
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e

4
go.sum
View File

@ -23,8 +23,8 @@ github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWV
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0 h1:fuFvfwIc+cpySYurvDNTs5LIHXP9Cj3reVRplj9Whv4= github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0 h1:fuFvfwIc+cpySYurvDNTs5LIHXP9Cj3reVRplj9Whv4=

View File

@ -6,9 +6,6 @@ package epubimageprocessing
import ( import (
"fmt" "fmt"
"image" "image"
_ "image/jpeg"
_ "image/png"
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -19,16 +16,8 @@ import (
epubimagefilters "github.com/celogeek/go-comic-converter/v2/internal/epub/imagefilters" epubimagefilters "github.com/celogeek/go-comic-converter/v2/internal/epub/imagefilters"
epubprogress "github.com/celogeek/go-comic-converter/v2/internal/epub/progress" epubprogress "github.com/celogeek/go-comic-converter/v2/internal/epub/progress"
"github.com/disintegration/gift" "github.com/disintegration/gift"
_ "golang.org/x/image/webp"
) )
type tasks struct {
Id int
Reader io.Reader
Path string
Name string
}
// only accept jpg, png and webp as source file // only accept jpg, png and webp as source file
func isSupportedImage(path string) bool { func isSupportedImage(path string) bool {
switch strings.ToLower(filepath.Ext(path)) { switch strings.ToLower(filepath.Ext(path)) {
@ -104,13 +93,7 @@ func LoadImages(o *Options) ([]*epubimage.Image, error) {
defer wg.Done() defer wg.Done()
for img := range imageInput { for img := range imageInput {
// Decode image src := img.Image
src, _, err := image.Decode(img.Reader)
if err != nil {
bar.Clear()
fmt.Fprintf(os.Stderr, "error processing image %s%s: %s\n", img.Path, img.Name, err)
os.Exit(1)
}
g := epubimagefilters.NewGift(src, o.Image) g := epubimagefilters.NewGift(src, o.Image)
// Convert image // Convert image

View File

@ -5,20 +5,32 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"image"
_ "image/jpeg"
_ "image/png"
"io" "io"
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"sync"
_ "golang.org/x/image/webp"
epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image" epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
"github.com/celogeek/go-comic-converter/v2/internal/sortpath" "github.com/celogeek/go-comic-converter/v2/internal/sortpath"
"github.com/nwaples/rardecode" "github.com/nwaples/rardecode/v2"
pdfimage "github.com/raff/pdfreader/image" pdfimage "github.com/raff/pdfreader/image"
"github.com/raff/pdfreader/pdfread" "github.com/raff/pdfreader/pdfread"
"golang.org/x/image/tiff"
) )
type tasks struct {
Id int
Image image.Image
Path string
Name string
}
type Options struct { type Options struct {
Input string Input string
SortPathMode int SortPathMode int
@ -30,28 +42,6 @@ type Options struct {
var errNoImagesFound = errors.New("no images found") var errNoImagesFound = errors.New("no images found")
// ensure copy image into a buffer
func (o *Options) mustExtractImage(imageOpener func() (io.ReadCloser, error)) *bytes.Buffer {
var b bytes.Buffer
if o.Dry {
return &b
}
f, err := imageOpener()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer f.Close()
_, err = io.Copy(&b, f)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
return &b
}
// load a directory of images // load a directory of images
func (o *Options) loadDir() (totalImages int, output chan *tasks, err error) { func (o *Options) loadDir() (totalImages int, output chan *tasks, err error) {
images := make([]string, 0) images := make([]string, 0)
@ -81,25 +71,64 @@ func (o *Options) loadDir() (totalImages int, output chan *tasks, err error) {
sort.Sort(sortpath.By(images, o.SortPathMode)) sort.Sort(sortpath.By(images, o.SortPathMode))
output = make(chan *tasks, o.Workers*2) // Queue all file with id
type job struct {
Id int
Path string
}
jobs := make(chan *job)
go func() { go func() {
defer close(output) defer close(jobs)
for i, img := range images { for i, path := range images {
p, fn := filepath.Split(img) jobs <- &job{i, path}
if p == input {
p = ""
} else {
p = p[len(input)+1:]
}
output <- &tasks{
Id: i,
Reader: o.mustExtractImage(func() (io.ReadCloser, error) { return os.Open(img) }),
Path: p,
Name: fn,
}
} }
}() }()
// read in parallel and get an image
output = make(chan *tasks, o.Workers)
wg := &sync.WaitGroup{}
wg.Add(o.Workers)
for j := 0; j < o.Workers; j++ {
go func() {
defer wg.Done()
for job := range jobs {
var img image.Image
if !o.Dry {
f, err := os.Open(job.Path)
if err != nil {
fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", job.Path, err)
os.Exit(1)
}
img, _, err = image.Decode(f)
if err != nil {
fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", job.Path, err)
os.Exit(1)
}
f.Close()
}
p, fn := filepath.Split(job.Path)
if p == input {
p = ""
} else {
p = p[len(input)+1:]
}
output <- &tasks{
Id: job.Id,
Image: img,
Path: p,
Name: fn,
}
}
}()
}
// wait all done and close
go func() {
wg.Wait()
close(output)
}()
return return
} }
@ -136,50 +165,76 @@ func (o *Options) loadCbz() (totalImages int, output chan *tasks, err error) {
indexedNames[name] = i indexedNames[name] = i
} }
output = make(chan *tasks, o.Workers*2) type job struct {
Id int
F *zip.File
}
jobs := make(chan *job)
go func() { go func() {
defer close(output) defer close(jobs)
defer r.Close()
for _, img := range images { for _, img := range images {
p, fn := filepath.Split(filepath.Clean(img.Name)) jobs <- &job{indexedNames[img.Name], img}
output <- &tasks{
Id: indexedNames[img.Name],
Reader: o.mustExtractImage(img.Open),
Path: p,
Name: fn,
}
} }
}() }()
output = make(chan *tasks, o.Workers)
wg := &sync.WaitGroup{}
wg.Add(o.Workers)
for j := 0; j < o.Workers; j++ {
go func() {
defer wg.Done()
for job := range jobs {
var img image.Image
if !o.Dry {
f, err := job.F.Open()
if err != nil {
fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", job.F.Name, err)
os.Exit(1)
}
img, _, err = image.Decode(f)
if err != nil {
fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", job.F.Name, err)
os.Exit(1)
}
f.Close()
}
p, fn := filepath.Split(filepath.Clean(job.F.Name))
output <- &tasks{
Id: job.Id,
Image: img,
Path: p,
Name: fn,
}
}
}()
}
go func() {
wg.Wait()
close(output)
r.Close()
}()
return return
} }
// load a rar file that include images // load a rar file that include images
func (o *Options) loadCbr() (totalImages int, output chan *tasks, err error) { func (o *Options) loadCbr() (totalImages int, output chan *tasks, err error) {
// listing and indexing var isSolid bool
rl, err := rardecode.OpenReader(o.Input, "") files, err := rardecode.List(o.Input)
if err != nil { if err != nil {
return return
} }
names := make([]string, 0) names := make([]string, 0)
for { for _, f := range files {
f, ferr := rl.Next()
if ferr != nil && ferr != io.EOF {
rl.Close()
err = ferr
return
}
if f == nil {
break
}
if !f.IsDir && isSupportedImage(f.Name) { if !f.IsDir && isSupportedImage(f.Name) {
if f.Solid {
isSolid = true
}
names = append(names, f.Name) names = append(names, f.Name)
} }
} }
rl.Close()
totalImages = len(names) totalImages = len(names)
if totalImages == 0 { if totalImages == 0 {
@ -194,46 +249,89 @@ func (o *Options) loadCbr() (totalImages int, output chan *tasks, err error) {
indexedNames[name] = i indexedNames[name] = i
} }
// send file to the queue type job struct {
output = make(chan *tasks, o.Workers*2) Id int
Name string
Open func() (io.ReadCloser, error)
}
jobs := make(chan *job)
go func() { go func() {
defer close(output) defer close(jobs)
r, err := rardecode.OpenReader(o.Input, "") if isSolid && !o.Dry {
if err != nil { r, rerr := rardecode.OpenReader(o.Input)
fmt.Fprintln(os.Stderr, err) if rerr != nil {
os.Exit(1) fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", o.Input, rerr)
}
defer r.Close()
for {
f, err := r.Next()
if err != nil && err != io.EOF {
fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
} }
if f == nil { defer r.Close()
break for {
} f, rerr := r.Next()
if idx, ok := indexedNames[f.Name]; ok { if rerr != nil {
var b bytes.Buffer if rerr == io.EOF {
if !o.Dry { break
io.Copy(&b, r) }
fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", f.Name, rerr)
os.Exit(1)
} }
if i, ok := indexedNames[f.Name]; ok {
p, fn := filepath.Split(filepath.Clean(f.Name)) var b bytes.Buffer
_, rerr = io.Copy(&b, r)
output <- &tasks{ if rerr != nil {
Id: idx, fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", f.Name, rerr)
Reader: &b, os.Exit(1)
Path: p, }
Name: fn, jobs <- &job{i, f.Name, func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(b.Bytes())), nil
}}
}
}
} else {
for _, img := range files {
if i, ok := indexedNames[img.Name]; ok {
jobs <- &job{i, img.Name, img.Open}
} }
} }
} }
}() }()
// send file to the queue
output = make(chan *tasks, o.Workers)
wg := &sync.WaitGroup{}
wg.Add(o.Workers)
for j := 0; j < o.Workers; j++ {
go func() {
defer wg.Done()
for job := range jobs {
var img image.Image
if !o.Dry {
f, err := job.Open()
if err != nil {
fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", job.Name, err)
os.Exit(1)
}
img, _, err = image.Decode(f)
if err != nil {
fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", job.Name, err)
os.Exit(1)
}
f.Close()
}
p, fn := filepath.Split(filepath.Clean(job.Name))
output <- &tasks{
Id: job.Id,
Image: img,
Path: p,
Name: fn,
}
}
}()
}
go func() {
wg.Wait()
close(output)
}()
return return
} }
@ -252,16 +350,9 @@ func (o *Options) loadPdf() (totalImages int, output chan *tasks, err error) {
defer close(output) defer close(output)
defer pdf.Close() defer pdf.Close()
for i := 0; i < totalImages; i++ { for i := 0; i < totalImages; i++ {
var b bytes.Buffer var img image.Image
if !o.Dry { if !o.Dry {
img, err := pdfimage.Extract(pdf, i+1) img, err = pdfimage.Extract(pdf, i+1)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
err = tiff.Encode(&b, img, nil)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
@ -269,10 +360,10 @@ func (o *Options) loadPdf() (totalImages int, output chan *tasks, err error) {
} }
output <- &tasks{ output <- &tasks{
Id: i, Id: i,
Reader: &b, Image: img,
Path: "", Path: "",
Name: fmt.Sprintf(pageFmt, i+1), Name: fmt.Sprintf(pageFmt, i+1),
} }
} }
}() }()