Compare commits

..

14 Commits

Author SHA1 Message Date
93294ba1f9
Merge branch 'reorg-packaging' 2024-05-12 08:19:11 +02:00
cdd3668ca2
improve readability main 2024-05-11 15:44:47 +02:00
30ac67b06d
move to pkg 2024-05-11 15:30:52 +02:00
577d51a819
rename epub 2024-05-11 14:31:27 +02:00
a76f29e91d
remove pointer from main 2024-05-11 14:27:27 +02:00
3c582ae52d
remove pointer from epub processor 2024-05-11 14:27:09 +02:00
a351106eb7
remove pointer from epub template 2024-05-11 14:26:59 +02:00
3d31108aba
remove pointer from epub zip 2024-05-11 14:26:51 +02:00
a94e67dd06
remove pointer from epub filters 2024-05-11 14:26:04 +02:00
5404390fd8
remove pointer from epub image 2024-05-11 14:25:52 +02:00
b1e246240c
remove pointer from profiles 2024-05-11 14:25:20 +02:00
e10eeadc4e
remove pointer from image options 2024-05-10 18:10:07 +02:00
5c7775cd47
simplify profiles 2024-05-10 18:05:51 +02:00
113e7ebcb0
update deps 2024-05-10 14:08:16 +02:00
46 changed files with 517 additions and 486 deletions

8
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0 github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0
github.com/schollz/progressbar/v3 v3.14.2 github.com/schollz/progressbar/v3 v3.14.2
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
golang.org/x/image v0.15.0 golang.org/x/image v0.16.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@ -23,7 +23,7 @@ require (
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/stretchr/testify v1.9.0 // indirect github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/net v0.24.0 // indirect golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.19.0 // indirect golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.19.0 // indirect golang.org/x/term v0.20.0 // indirect
) )

16
go.sum
View File

@ -39,17 +39,17 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2oeKxpIUmtiDV5sn71VgeQgg6vcE7k= github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2oeKxpIUmtiDV5sn71VgeQgg6vcE7k=
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM= github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -1,72 +0,0 @@
// Package epuboptions Options for EPUB creation.
package epuboptions
import "fmt"
type Crop struct {
Enabled bool
Left, Up, Right, Bottom int
Limit int
SkipIfLimitReached bool
}
type Color struct {
Foreground, Background string
}
type View struct {
Width, Height int
AspectRatio float64
PortraitOnly bool
Color Color
}
type Image struct {
Crop *Crop
Quality int
Brightness int
Contrast int
AutoContrast bool
AutoRotate bool
AutoSplitDoublePage bool
KeepDoublePageIfSplit bool
KeepSplitDoublePageAspect bool
NoBlankImage bool
Manga bool
HasCover bool
View *View
GrayScale bool
GrayScaleMode int
Resize bool
Format string
AppleBookCompatibility bool
}
type Options struct {
Input string
Output string
Title string
TitlePage int
Author string
LimitMb int
StripFirstDirectoryFromToc bool
Dry bool
DryVerbose bool
SortPathMode int
Quiet bool
Json bool
Workers int
Image *Image
}
func (o *Options) WorkersRatio(pct int) (nbWorkers int) {
nbWorkers = o.Workers * pct / 100
if nbWorkers < 1 {
nbWorkers = 1
}
return
}
func (o *Options) ImgStorage() string {
return fmt.Sprintf("%s.tmp", o.Output)
}

View File

@ -1,21 +0,0 @@
// Package epubtemplates Templates use to create xml files of the EPUB.
package epubtemplates
import _ "embed"
var (
//go:embed "epub_templates_container.xml.tmpl"
Container string
//go:embed "epub_templates_applebooks.xml.tmpl"
AppleBooks string
//go:embed "epub_templates_style.css.tmpl"
Style string
//go:embed "epub_templates_text.xhtml.tmpl"
Text string
//go:embed "epub_templates_blank.xhtml.tmpl"
Blank string
)

View File

@ -1,100 +0,0 @@
package epubzip
import (
"archive/zip"
"image"
"os"
"sync"
)
type StorageImageWriter struct {
fh *os.File
fz *zip.Writer
format string
mut *sync.Mutex
}
func NewStorageImageWriter(filename string, format string) (*StorageImageWriter, error) {
fh, err := os.Create(filename)
if err != nil {
return nil, err
}
fz := zip.NewWriter(fh)
return &StorageImageWriter{fh, fz, format, &sync.Mutex{}}, nil
}
func (e *StorageImageWriter) Close() error {
if err := e.fz.Close(); err != nil {
_ = e.fh.Close()
return err
}
return e.fh.Close()
}
func (e *StorageImageWriter) Add(filename string, img image.Image, quality int) error {
zipImage, err := CompressImage(filename, e.format, img, quality)
if err != nil {
return err
}
e.mut.Lock()
defer e.mut.Unlock()
fh, err := e.fz.CreateRaw(zipImage.Header)
if err != nil {
return err
}
_, err = fh.Write(zipImage.Data)
if err != nil {
return err
}
return nil
}
type StorageImageReader struct {
filename string
fh *os.File
fz *zip.Reader
files map[string]*zip.File
}
func NewStorageImageReader(filename string) (*StorageImageReader, error) {
fh, err := os.Open(filename)
if err != nil {
return nil, err
}
s, err := fh.Stat()
if err != nil {
return nil, err
}
fz, err := zip.NewReader(fh, s.Size())
if err != nil {
return nil, err
}
files := map[string]*zip.File{}
for _, z := range fz.File {
files[z.Name] = z
}
return &StorageImageReader{filename, fh, fz, files}, nil
}
func (e *StorageImageReader) Get(filename string) *zip.File {
return e.files[filename]
}
func (e *StorageImageReader) Size(filename string) uint64 {
img := e.Get(filename)
if img != nil {
return img.CompressedSize64 + 30 + uint64(len(img.Name))
}
return 0
}
func (e *StorageImageReader) Close() error {
return e.fh.Close()
}
func (e *StorageImageReader) Remove() error {
return os.Remove(e.filename)
}

View File

@ -18,27 +18,26 @@ import (
"strings" "strings"
"time" "time"
"github.com/celogeek/go-comic-converter/v2/internal/converter/options" "github.com/celogeek/go-comic-converter/v2/internal/pkg/utils"
"github.com/celogeek/go-comic-converter/v2/internal/utils"
) )
type Converter struct { type Converter struct {
Options *options.Options Options *Options
Cmd *flag.FlagSet Cmd *flag.FlagSet
order []converterOrder order []order
isZeroValueErrs []error isZeroValueErrs []error
startAt time.Time startAt time.Time
} }
// New Create a new parser // New Create a new parser
func New() *Converter { func New() *Converter {
o := options.New() o := NewOptions()
cmd := flag.NewFlagSet("go-comic-converter", flag.ExitOnError) cmd := flag.NewFlagSet("go-comic-converter", flag.ExitOnError)
conv := &Converter{ conv := &Converter{
Options: o, Options: o,
Cmd: cmd, Cmd: cmd,
order: make([]converterOrder, 0), order: make([]order, 0),
startAt: time.Now(), startAt: time.Now(),
} }
@ -48,9 +47,9 @@ func New() *Converter {
utils.Printf("Usage of %s:\n", filepath.Base(os.Args[0])) utils.Printf("Usage of %s:\n", filepath.Base(os.Args[0]))
for _, o := range conv.order { for _, o := range conv.order {
switch v := o.(type) { switch v := o.(type) {
case converterOrderSection: case orderSection:
utils.Printf("\n%s:\n", o.Value()) utils.Printf("\n%s:\n", o.Value())
case converterOrderName: case orderName:
utils.Println(conv.Usage(v.isString, cmd.Lookup(v.Value()))) utils.Println(conv.Usage(v.isString, cmd.Lookup(v.Value())))
} }
} }
@ -69,31 +68,31 @@ func (c *Converter) LoadConfig() error {
// AddSection Create a new section of config // AddSection Create a new section of config
func (c *Converter) AddSection(section string) { func (c *Converter) AddSection(section string) {
c.order = append(c.order, converterOrderSection{value: section}) c.order = append(c.order, orderSection{value: section})
} }
// AddStringParam Add a string parameter // AddStringParam Add a string parameter
func (c *Converter) AddStringParam(p *string, name string, value string, usage string) { func (c *Converter) AddStringParam(p *string, name string, value string, usage string) {
c.Cmd.StringVar(p, name, value, usage) c.Cmd.StringVar(p, name, value, usage)
c.order = append(c.order, converterOrderName{value: name, isString: true}) c.order = append(c.order, orderName{value: name, isString: true})
} }
// AddIntParam Add an integer parameter // AddIntParam Add an integer parameter
func (c *Converter) AddIntParam(p *int, name string, value int, usage string) { func (c *Converter) AddIntParam(p *int, name string, value int, usage string) {
c.Cmd.IntVar(p, name, value, usage) c.Cmd.IntVar(p, name, value, usage)
c.order = append(c.order, converterOrderName{value: name}) c.order = append(c.order, orderName{value: name})
} }
// AddFloatParam Add an float parameter // AddFloatParam Add an float parameter
func (c *Converter) AddFloatParam(p *float64, name string, value float64, usage string) { func (c *Converter) AddFloatParam(p *float64, name string, value float64, usage string) {
c.Cmd.Float64Var(p, name, value, usage) c.Cmd.Float64Var(p, name, value, usage)
c.order = append(c.order, converterOrderName{value: name}) c.order = append(c.order, orderName{value: name})
} }
// AddBoolParam Add a boolean parameter // AddBoolParam Add a boolean parameter
func (c *Converter) AddBoolParam(p *bool, name string, value bool, usage string) { func (c *Converter) AddBoolParam(p *bool, name string, value bool, usage string) {
c.Cmd.BoolVar(p, name, value, usage) c.Cmd.BoolVar(p, name, value, usage)
c.order = append(c.order, converterOrderName{value: name}) c.order = append(c.order, orderName{value: name})
} }
// InitParse Initialize the parser with all section and parameter. // InitParse Initialize the parser with all section and parameter.

View File

@ -1,5 +1,5 @@
// Package options manage options with default value from config. // Package options manage options with default value from config.
package options package converter
import ( import (
"encoding/json" "encoding/json"
@ -9,8 +9,6 @@ import (
"strings" "strings"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/celogeek/go-comic-converter/v2/internal/converter/profiles"
) )
type Options struct { type Options struct {
@ -77,11 +75,11 @@ type Options struct {
Help bool `yaml:"-"` Help bool `yaml:"-"`
// Internal // Internal
profiles profiles.Profiles profiles Profiles
} }
// New Initialize default options. // NewOptions Initialize default options.
func New() *Options { func NewOptions() *Options {
return &Options{ return &Options{
Profile: "SR", Profile: "SR",
Quality: 85, Quality: 85,
@ -100,7 +98,7 @@ func New() *Options {
BackgroundColor: "FFF", BackgroundColor: "FFF",
Format: "jpeg", Format: "jpeg",
TitlePage: 1, TitlePage: 1,
profiles: profiles.New(), profiles: NewProfiles(),
} }
} }
@ -310,7 +308,7 @@ func (o *Options) ShowConfig() string {
// ResetConfig reset all settings to default value // ResetConfig reset all settings to default value
func (o *Options) ResetConfig() error { func (o *Options) ResetConfig() error {
if err := New().SaveConfig(); err != nil { if err := NewOptions().SaveConfig(); err != nil {
return err return err
} }
return o.LoadConfig() return o.LoadConfig()
@ -329,8 +327,11 @@ func (o *Options) SaveConfig() error {
} }
// GetProfile shortcut to get current profile // GetProfile shortcut to get current profile
func (o *Options) GetProfile() *profiles.Profile { func (o *Options) GetProfile() *Profile {
return o.profiles.Get(o.Profile) if p, ok := o.profiles[o.Profile]; ok {
return &p
}
return nil
} }
// AvailableProfiles all available profiles // AvailableProfiles all available profiles

View File

@ -1,27 +1,27 @@
package converter package converter
// Name or Section // Name or Section
type converterOrder interface { type order interface {
Value() string Value() string
} }
// Section // Section
type converterOrderSection struct { type orderSection struct {
value string value string
} }
func (s converterOrderSection) Value() string { func (s orderSection) Value() string {
return s.value return s.value
} }
// Name // Name
// //
// isString is used to quote the default value. // isString is used to quote the default value.
type converterOrderName struct { type orderName struct {
value string value string
isString bool isString bool
} }
func (s converterOrderName) Value() string { func (s orderName) Value() string {
return s.value return s.value
} }

View File

@ -1,5 +1,5 @@
// Package profiles manage supported profiles for go-comic-converter. // Package profiles manage supported profiles for go-comic-converter.
package profiles package converter
import ( import (
"fmt" "fmt"
@ -13,11 +13,12 @@ type Profile struct {
Height int `json:"height"` Height int `json:"height"`
} }
type Profiles []Profile type Profiles map[string]Profile
// New Initialize list of all supported profiles. // NewProfiles Initialize list of all supported profiles.
func New() Profiles { func NewProfiles() Profiles {
return []Profile{ res := make(Profiles)
for _, r := range []Profile{
// High Resolution for Tablet // High Resolution for Tablet
{"HR", "High Resolution", 2400, 3840}, {"HR", "High Resolution", 2400, 3840},
{"SR", "Standard Resolution", 1200, 1920}, {"SR", "Standard Resolution", 1200, 1920},
@ -50,7 +51,10 @@ func New() Profiles {
// reMarkable // reMarkable
{"RM1", "reMarkable 1", 1404, 1872}, {"RM1", "reMarkable 1", 1404, 1872},
{"RM2", "reMarkable 2", 1404, 1872}, {"RM2", "reMarkable 2", 1404, 1872},
} {
res[r.Code] = r
} }
return res
} }
func (p Profiles) String() string { func (p Profiles) String() string {
@ -65,13 +69,3 @@ func (p Profiles) String() string {
} }
return strings.Join(s, "\n") return strings.Join(s, "\n")
} }
// Get Lookup profile by code
func (p Profiles) Get(name string) *Profile {
for _, profile := range p {
if profile.Code == name {
return &profile
}
}
return nil
}

View File

@ -14,34 +14,33 @@ import (
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubimage"
epubimageprocessor "github.com/celogeek/go-comic-converter/v2/internal/epub/imageprocessor" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubimageprocessor"
epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epuboptions"
epubprogress "github.com/celogeek/go-comic-converter/v2/internal/epub/progress" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubprogress"
epubtemplates "github.com/celogeek/go-comic-converter/v2/internal/epub/templates" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubtemplates"
epubtree "github.com/celogeek/go-comic-converter/v2/internal/epub/tree" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubtree"
epubzip "github.com/celogeek/go-comic-converter/v2/internal/epub/zip" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubzip"
"github.com/celogeek/go-comic-converter/v2/internal/utils" "github.com/celogeek/go-comic-converter/v2/internal/pkg/utils"
) )
type EPub struct { type EPUB struct {
*epuboptions.Options epuboptions.EPUBOptions
UID string UID string
Publisher string Publisher string
UpdatedAt string UpdatedAt string
templateProcessor *template.Template templateProcessor *template.Template
imageProcessor *epubimageprocessor.EPUBImageProcessor imageProcessor epubimageprocessor.EPUBImageProcessor
} }
type epubPart struct { type epubPart struct {
Cover *epubimage.Image Cover epubimage.EPUBImage
Images []*epubimage.Image Images []epubimage.EPUBImage
Reader *zip.ReadCloser
} }
// New initialize EPUB // New initialize EPUB
func New(options *epuboptions.Options) *EPub { func New(options epuboptions.EPUBOptions) EPUB {
uid := uuid.Must(uuid.NewV4()) uid := uuid.Must(uuid.NewV4())
tmpl := template.New("parser") tmpl := template.New("parser")
tmpl.Funcs(template.FuncMap{ tmpl.Funcs(template.FuncMap{
@ -49,8 +48,8 @@ func New(options *epuboptions.Options) *EPub {
"zoom": func(s int, z float32) int { return int(float32(s) * z) }, "zoom": func(s int, z float32) int { return int(float32(s) * z) },
}) })
return &EPub{ return EPUB{
Options: options, EPUBOptions: options,
UID: uid.String(), UID: uid.String(),
Publisher: "GO Comic Converter", Publisher: "GO Comic Converter",
UpdatedAt: time.Now().UTC().Format("2006-01-02T15:04:05Z"), UpdatedAt: time.Now().UTC().Format("2006-01-02T15:04:05Z"),
@ -60,7 +59,7 @@ func New(options *epuboptions.Options) *EPub {
} }
// render templates // render templates
func (e *EPub) render(templateString string, data map[string]any) string { func (e EPUB) render(templateString string, data map[string]any) string {
var result strings.Builder var result strings.Builder
tmpl := template.Must(e.templateProcessor.Parse(templateString)) tmpl := template.Must(e.templateProcessor.Parse(templateString))
if err := tmpl.Execute(&result, data); err != nil { if err := tmpl.Execute(&result, data); err != nil {
@ -70,7 +69,7 @@ func (e *EPub) render(templateString string, data map[string]any) string {
} }
// write image to the zip // write image to the zip
func (e *EPub) writeImage(wz *epubzip.EPUBZip, img *epubimage.Image, zipImg *zip.File) error { func (e EPUB) writeImage(wz epubzip.EPUBZip, img epubimage.EPUBImage, zipImg *zip.File) error {
err := wz.WriteContent( err := wz.WriteContent(
img.EPUBPagePath(), img.EPUBPagePath(),
[]byte(e.render(epubtemplates.Text, map[string]any{ []byte(e.render(epubtemplates.Text, map[string]any{
@ -88,7 +87,7 @@ func (e *EPub) writeImage(wz *epubzip.EPUBZip, img *epubimage.Image, zipImg *zip
} }
// write blank page // write blank page
func (e *EPub) writeBlank(wz *epubzip.EPUBZip, img *epubimage.Image) error { func (e EPUB) writeBlank(wz epubzip.EPUBZip, img epubimage.EPUBImage) error {
return wz.WriteContent( return wz.WriteContent(
img.EPUBSpacePath(), img.EPUBSpacePath(),
[]byte(e.render(epubtemplates.Blank, map[string]any{ []byte(e.render(epubtemplates.Blank, map[string]any{
@ -99,7 +98,7 @@ func (e *EPub) writeBlank(wz *epubzip.EPUBZip, img *epubimage.Image) error {
} }
// write title image // write title image
func (e *EPub) writeCoverImage(wz *epubzip.EPUBZip, img *epubimage.Image, part, totalParts int) error { func (e EPUB) writeCoverImage(wz epubzip.EPUBZip, img epubimage.EPUBImage, part, totalParts int) error {
title := "Cover" title := "Cover"
text := "" text := ""
if totalParts > 1 { if totalParts > 1 {
@ -119,7 +118,7 @@ func (e *EPub) writeCoverImage(wz *epubzip.EPUBZip, img *epubimage.Image, part,
return err return err
} }
coverTitle, err := e.imageProcessor.CoverTitleData(&epubimageprocessor.CoverTitleDataOptions{ coverTitle, err := e.imageProcessor.CoverTitleData(epubimageprocessor.CoverTitleDataOptions{
Src: img.Raw, Src: img.Raw,
Name: "cover", Name: "cover",
Text: text, Text: text,
@ -142,7 +141,7 @@ func (e *EPub) writeCoverImage(wz *epubzip.EPUBZip, img *epubimage.Image, part,
} }
// write title image // write title image
func (e *EPub) writeTitleImage(wz *epubzip.EPUBZip, img *epubimage.Image, title string) error { func (e EPUB) writeTitleImage(wz epubzip.EPUBZip, img epubimage.EPUBImage, title string) error {
titleAlign := "" titleAlign := ""
if !e.Image.View.PortraitOnly { if !e.Image.View.PortraitOnly {
if e.Image.Manga { if e.Image.Manga {
@ -176,7 +175,7 @@ func (e *EPub) writeTitleImage(wz *epubzip.EPUBZip, img *epubimage.Image, title
return err return err
} }
coverTitle, err := e.imageProcessor.CoverTitleData(&epubimageprocessor.CoverTitleDataOptions{ coverTitle, err := e.imageProcessor.CoverTitleData(epubimageprocessor.CoverTitleDataOptions{
Src: img.Raw, Src: img.Raw,
Name: "title", Name: "title",
Text: title, Text: title,
@ -198,11 +197,11 @@ func (e *EPub) writeTitleImage(wz *epubzip.EPUBZip, img *epubimage.Image, title
} }
// extract image and split it into part // extract image and split it into part
func (e *EPub) getParts() (parts []*epubPart, imgStorage *epubzip.StorageImageReader, err error) { func (e EPUB) getParts() (parts []epubPart, imgStorage epubzip.StorageImageReader, err error) {
images, err := e.imageProcessor.Load() images, err := e.imageProcessor.Load()
if err != nil { if err != nil {
return nil, nil, err return
} }
// sort result by id and part // sort result by id and part
@ -213,23 +212,23 @@ func (e *EPub) getParts() (parts []*epubPart, imgStorage *epubzip.StorageImageRe
return images[i].Id < images[j].Id return images[i].Id < images[j].Id
}) })
parts = make([]*epubPart, 0) parts = make([]epubPart, 0)
cover := images[0] cover := images[0]
if e.Image.HasCover { if e.Image.HasCover {
images = images[1:] images = images[1:]
} }
if e.Dry { if e.Dry {
parts = append(parts, &epubPart{ parts = append(parts, epubPart{
Cover: cover, Cover: cover,
Images: images, Images: images,
}) })
return parts, nil, nil return
} }
imgStorage, err = epubzip.NewStorageImageReader(e.ImgStorage()) imgStorage, err = epubzip.NewStorageImageReader(e.ImgStorage())
if err != nil { if err != nil {
return nil, nil, err return
} }
// compute size of the EPUB part and try to be as close as possible of the target // compute size of the EPUB part and try to be as close as possible of the target
@ -239,37 +238,37 @@ func (e *EPub) getParts() (parts []*epubPart, imgStorage *epubzip.StorageImageRe
baseSize := uint64(128*1024) + imgStorage.Size(cover.EPUBImgPath())*2 baseSize := uint64(128*1024) + imgStorage.Size(cover.EPUBImgPath())*2
currentSize := baseSize currentSize := baseSize
currentImages := make([]*epubimage.Image, 0) currentImages := make([]epubimage.EPUBImage, 0)
part := 1 part := 1
for _, img := range images { for _, img := range images {
imgSize := imgStorage.Size(img.EPUBImgPath()) + xhtmlSize imgSize := imgStorage.Size(img.EPUBImgPath()) + xhtmlSize
if maxSize > 0 && len(currentImages) > 0 && currentSize+imgSize > maxSize { if maxSize > 0 && len(currentImages) > 0 && currentSize+imgSize > maxSize {
parts = append(parts, &epubPart{ parts = append(parts, epubPart{
Cover: cover, Cover: cover,
Images: currentImages, Images: currentImages,
}) })
part += 1 part += 1
currentSize = baseSize currentSize = baseSize
currentImages = make([]*epubimage.Image, 0) currentImages = make([]epubimage.EPUBImage, 0)
} }
currentSize += imgSize currentSize += imgSize
currentImages = append(currentImages, img) currentImages = append(currentImages, img)
} }
if len(currentImages) > 0 { if len(currentImages) > 0 {
parts = append(parts, &epubPart{ parts = append(parts, epubPart{
Cover: cover, Cover: cover,
Images: currentImages, Images: currentImages,
}) })
} }
return parts, imgStorage, nil return
} }
// create a tree from the directories. // create a tree from the directories.
// //
// this is used to simulate the toc. // this is used to simulate the toc.
func (e *EPub) getTree(images []*epubimage.Image, skipFiles bool) string { func (e EPUB) getTree(images []epubimage.EPUBImage, skipFiles bool) string {
t := epubtree.New() t := epubtree.New()
for _, img := range images { for _, img := range images {
if skipFiles { if skipFiles {
@ -286,7 +285,7 @@ func (e *EPub) getTree(images []*epubimage.Image, skipFiles bool) string {
return c.WriteString("") return c.WriteString("")
} }
func (e *EPub) computeAspectRatio(epubParts []*epubPart) float64 { func (e EPUB) computeAspectRatio(epubParts []epubPart) float64 {
var ( var (
bestAspectRatio float64 bestAspectRatio float64
bestAspectRatioCount int bestAspectRatioCount int
@ -313,7 +312,7 @@ func (e *EPub) computeAspectRatio(epubParts []*epubPart) float64 {
return bestAspectRatio return bestAspectRatio
} }
func (e *EPub) computeViewPort(epubParts []*epubPart) { func (e EPUB) computeViewPort(epubParts []epubPart) {
if e.Image.View.AspectRatio == -1 { if e.Image.View.AspectRatio == -1 {
return //keep device size return //keep device size
} }
@ -332,14 +331,14 @@ func (e *EPub) computeViewPort(epubParts []*epubPart) {
} }
} }
func (e *EPub) writePart(path string, currentPart, totalParts int, part *epubPart, imgStorage *epubzip.StorageImageReader) error { func (e EPUB) writePart(path string, currentPart, totalParts int, part epubPart, imgStorage epubzip.StorageImageReader) error {
hasTitlePage := e.TitlePage == 1 || (e.TitlePage == 2 && totalParts > 1) hasTitlePage := e.TitlePage == 1 || (e.TitlePage == 2 && totalParts > 1)
wz, err := epubzip.New(path) wz, err := epubzip.New(path)
if err != nil { if err != nil {
return err return err
} }
defer func(wz *epubzip.EPUBZip) { defer func(wz epubzip.EPUBZip) {
_ = wz.Close() _ = wz.Close()
}(wz) }(wz)
@ -355,7 +354,7 @@ func (e *EPub) writePart(path string, currentPart, totalParts int, part *epubPar
content := []zipContent{ content := []zipContent{
{"META-INF/container.xml", epubtemplates.Container}, {"META-INF/container.xml", epubtemplates.Container},
{"META-INF/com.apple.ibooks.display-options.xml", epubtemplates.AppleBooks}, {"META-INF/com.apple.ibooks.display-options.xml", epubtemplates.AppleBooks},
{"OEBPS/content.opf", epubtemplates.Content(&epubtemplates.ContentOptions{ {"OEBPS/content.opf", epubtemplates.Content{
Title: title, Title: title,
HasTitlePage: hasTitlePage, HasTitlePage: hasTitlePage,
UID: e.UID, UID: e.UID,
@ -367,7 +366,7 @@ func (e *EPub) writePart(path string, currentPart, totalParts int, part *epubPar
Images: part.Images, Images: part.Images,
Current: currentPart, Current: currentPart,
Total: totalParts, Total: totalParts,
})}, }.String()},
{"OEBPS/toc.xhtml", epubtemplates.Toc(title, hasTitlePage, e.StripFirstDirectoryFromToc, part.Images)}, {"OEBPS/toc.xhtml", epubtemplates.Toc(title, hasTitlePage, e.StripFirstDirectoryFromToc, part.Images)},
{"OEBPS/Text/style.css", e.render(epubtemplates.Style, map[string]any{ {"OEBPS/Text/style.css", e.render(epubtemplates.Style, map[string]any{
"View": e.Image.View, "View": e.Image.View,
@ -413,7 +412,7 @@ func (e *EPub) writePart(path string, currentPart, totalParts int, part *epubPar
} }
// create the zip // create the zip
func (e *EPub) Write() error { func (e EPUB) Write() error {
epubParts, imgStorage, err := e.getParts() epubParts, imgStorage, err := e.getParts()
if err != nil { if err != nil {
return err return err
@ -424,7 +423,7 @@ func (e *EPub) Write() error {
utils.Printf("TOC:\n - %s\n%s\n", e.Title, e.getTree(p.Images, true)) utils.Printf("TOC:\n - %s\n%s\n", e.Title, e.getTree(p.Images, true))
if e.DryVerbose { if e.DryVerbose {
if e.Image.HasCover { if e.Image.HasCover {
utils.Printf("Cover:\n%s\n", e.getTree([]*epubimage.Image{p.Cover}, false)) utils.Printf("Cover:\n%s\n", e.getTree([]epubimage.EPUBImage{p.Cover}, false))
} }
utils.Printf("Files:\n%s\n", e.getTree(p.Images, false)) utils.Printf("Files:\n%s\n", e.getTree(p.Images, false))
} }

View File

@ -1,4 +1,4 @@
// Package epubimage Image helpers to transform image. // Package epubimage EPUBImage helpers to transform image.
package epubimage package epubimage
import ( import (
@ -7,7 +7,7 @@ import (
"strings" "strings"
) )
type Image struct { type EPUBImage struct {
Id int Id int
Part int Part int
Raw image.Image Raw image.Image
@ -24,47 +24,47 @@ type Image struct {
} }
// SpaceKey key name of the blank page after the image // SpaceKey key name of the blank page after the image
func (i *Image) SpaceKey() string { func (i EPUBImage) SpaceKey() string {
return fmt.Sprintf("space_%d", i.Id) return fmt.Sprintf("space_%d", i.Id)
} }
// SpacePath path of the blank page // SpacePath path of the blank page
func (i *Image) SpacePath() string { func (i EPUBImage) SpacePath() string {
return fmt.Sprintf("Text/%s.xhtml", i.SpaceKey()) return fmt.Sprintf("Text/%s.xhtml", i.SpaceKey())
} }
// EPUBSpacePath path of the blank page into the EPUB // EPUBSpacePath path of the blank page into the EPUB
func (i *Image) EPUBSpacePath() string { func (i EPUBImage) EPUBSpacePath() string {
return fmt.Sprintf("OEBPS/%s", i.SpacePath()) return fmt.Sprintf("OEBPS/%s", i.SpacePath())
} }
// PageKey key for page // PageKey key for page
func (i *Image) PageKey() string { func (i EPUBImage) PageKey() string {
return fmt.Sprintf("page_%d_p%d", i.Id, i.Part) return fmt.Sprintf("page_%d_p%d", i.Id, i.Part)
} }
// PagePath page path linked to the image // PagePath page path linked to the image
func (i *Image) PagePath() string { func (i EPUBImage) PagePath() string {
return fmt.Sprintf("Text/%s.xhtml", i.PageKey()) return fmt.Sprintf("Text/%s.xhtml", i.PageKey())
} }
// EPUBPagePath page path into the EPUB // EPUBPagePath page path into the EPUB
func (i *Image) EPUBPagePath() string { func (i EPUBImage) EPUBPagePath() string {
return fmt.Sprintf("OEBPS/%s", i.PagePath()) return fmt.Sprintf("OEBPS/%s", i.PagePath())
} }
// ImgKey key for image // ImgKey key for image
func (i *Image) ImgKey() string { func (i EPUBImage) ImgKey() string {
return fmt.Sprintf("img_%d_p%d", i.Id, i.Part) return fmt.Sprintf("img_%d_p%d", i.Id, i.Part)
} }
// ImgPath image path // ImgPath image path
func (i *Image) ImgPath() string { func (i EPUBImage) ImgPath() string {
return fmt.Sprintf("Images/%s.%s", i.ImgKey(), i.Format) return fmt.Sprintf("Images/%s.%s", i.ImgKey(), i.Format)
} }
// EPUBImgPath image path into the EPUB // EPUBImgPath image path into the EPUB
func (i *Image) EPUBImgPath() string { func (i EPUBImage) EPUBImgPath() string {
return fmt.Sprintf("OEBPS/%s", i.ImgPath()) return fmt.Sprintf("OEBPS/%s", i.ImgPath())
} }
@ -72,7 +72,7 @@ func (i *Image) EPUBImgPath() string {
// //
// center by default. // center by default.
// align to left or right if it's part of the split double page. // align to left or right if it's part of the split double page.
func (i *Image) ImgStyle(viewWidth, viewHeight int, align string) string { func (i EPUBImage) ImgStyle(viewWidth, viewHeight int, align string) string {
relWidth, relHeight := i.RelSize(viewWidth, viewHeight) relWidth, relHeight := i.RelSize(viewWidth, viewHeight)
marginW, marginH := float64(viewWidth-relWidth)/2, float64(viewHeight-relHeight)/2 marginW, marginH := float64(viewWidth-relWidth)/2, float64(viewHeight-relHeight)/2
@ -97,7 +97,7 @@ func (i *Image) ImgStyle(viewWidth, viewHeight int, align string) string {
return strings.Join(style, "; ") return strings.Join(style, "; ")
} }
func (i *Image) RelSize(viewWidth, viewHeight int) (relWidth, relHeight int) { func (i EPUBImage) RelSize(viewWidth, viewHeight int) (relWidth, relHeight int) {
w, h := viewWidth, viewHeight w, h := viewWidth, viewHeight
srcw, srch := i.Width, i.Height srcw, srch := i.Width, i.Height

View File

@ -10,14 +10,14 @@ import (
// AutoContrast Automatically improve contrast // AutoContrast Automatically improve contrast
func AutoContrast() gift.Filter { func AutoContrast() gift.Filter {
return &autocontrast{} return autocontrast{}
} }
type autocontrast struct { type autocontrast struct {
} }
// compute the color number between 0 and 1 that hold half of the pixel // compute the color number between 0 and 1 that hold half of the pixel
func (f *autocontrast) mean(src image.Image) float32 { func (f autocontrast) mean(src image.Image) float32 {
bucket := map[int]int{} bucket := map[int]int{}
for x := src.Bounds().Min.X; x < src.Bounds().Max.X; x++ { for x := src.Bounds().Min.X; x < src.Bounds().Max.X; x++ {
for y := src.Bounds().Min.Y; y < src.Bounds().Max.Y; y++ { for y := src.Bounds().Min.Y; y < src.Bounds().Max.Y; y++ {
@ -44,7 +44,7 @@ func (f *autocontrast) mean(src image.Image) float32 {
} }
// ensure value stay into 0 to 1 bound // ensure value stay into 0 to 1 bound
func (f *autocontrast) cap(v float32) float32 { func (f autocontrast) cap(v float32) float32 {
if v < 0 { if v < 0 {
return 0 return 0
} }
@ -55,12 +55,12 @@ func (f *autocontrast) cap(v float32) float32 {
} }
// power of 2 for float32 // power of 2 for float32
func (f *autocontrast) pow2(v float32) float32 { func (f autocontrast) pow2(v float32) float32 {
return v * v return v * v
} }
// Draw into the dst after applying the filter // Draw into the dst after applying the filter
func (f *autocontrast) Draw(dst draw.Image, src image.Image, options *gift.Options) { func (f autocontrast) Draw(dst draw.Image, src image.Image, options *gift.Options) {
// half of the pixel has this color idx // half of the pixel has this color idx
colorMean := f.mean(src) colorMean := f.mean(src)
@ -84,7 +84,7 @@ func (f *autocontrast) Draw(dst draw.Image, src image.Image, options *gift.Optio
} }
// Bounds calculates the appropriate bounds of an image after applying the filter. // Bounds calculates the appropriate bounds of an image after applying the filter.
func (*autocontrast) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { func (autocontrast) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
dstBounds = srcBounds dstBounds = srcBounds
return return
} }

View File

@ -13,7 +13,7 @@ import (
// CoverTitle Create a title with the cover image // CoverTitle Create a title with the cover image
func CoverTitle(title string, align string, pctWidth int, pctMargin int, maxFontSize int, borderSize int) gift.Filter { func CoverTitle(title string, align string, pctWidth int, pctMargin int, maxFontSize int, borderSize int) gift.Filter {
return &coverTitle{title, align, pctWidth, pctMargin, maxFontSize, borderSize} return coverTitle{title, align, pctWidth, pctMargin, maxFontSize, borderSize}
} }
type coverTitle struct { type coverTitle struct {
@ -26,12 +26,12 @@ type coverTitle struct {
} }
// Bounds size is the same as source // Bounds size is the same as source
func (p *coverTitle) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { func (p coverTitle) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
return srcBounds return srcBounds
} }
// Draw blur the src image, and create a box with the title in the middle // Draw blur the src image, and create a box with the title in the middle
func (p *coverTitle) Draw(dst draw.Image, src image.Image, _ *gift.Options) { func (p coverTitle) Draw(dst draw.Image, src image.Image, _ *gift.Options) {
draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src) draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
if p.title == "" { if p.title == "" {
return return

View File

@ -11,14 +11,14 @@ import (
// //
// This will cut in the middle of the page. // This will cut in the middle of the page.
func CropSplitDoublePage(right bool) gift.Filter { func CropSplitDoublePage(right bool) gift.Filter {
return &cropSplitDoublePage{right} return cropSplitDoublePage{right}
} }
type cropSplitDoublePage struct { type cropSplitDoublePage struct {
right bool right bool
} }
func (p *cropSplitDoublePage) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { func (p cropSplitDoublePage) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
if p.right { if p.right {
dstBounds = image.Rect( dstBounds = image.Rect(
srcBounds.Max.X/2, srcBounds.Min.Y, srcBounds.Max.X/2, srcBounds.Min.Y,
@ -33,6 +33,6 @@ func (p *cropSplitDoublePage) Bounds(srcBounds image.Rectangle) (dstBounds image
return return
} }
func (p *cropSplitDoublePage) Draw(dst draw.Image, src image.Image, options *gift.Options) { func (p cropSplitDoublePage) Draw(dst draw.Image, src image.Image, options *gift.Options) {
gift.Crop(dst.Bounds()).Draw(dst, src, options) gift.Crop(dst.Bounds()).Draw(dst, src, options)
} }

View File

@ -12,13 +12,13 @@ import (
// //
// An image 0x0 is not a valid image, and failed to read. // An image 0x0 is not a valid image, and failed to read.
func Pixel() gift.Filter { func Pixel() gift.Filter {
return &pixel{} return pixel{}
} }
type pixel struct { type pixel struct {
} }
func (p *pixel) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { func (p pixel) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
if srcBounds.Dx() == 0 || srcBounds.Dy() == 0 { if srcBounds.Dx() == 0 || srcBounds.Dy() == 0 {
dstBounds = image.Rect(0, 0, 1, 1) dstBounds = image.Rect(0, 0, 1, 1)
} else { } else {
@ -27,7 +27,7 @@ func (p *pixel) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
return return
} }
func (p *pixel) Draw(dst draw.Image, src image.Image, _ *gift.Options) { func (p pixel) Draw(dst draw.Image, src image.Image, _ *gift.Options) {
if dst.Bounds().Dx() == 1 && dst.Bounds().Dy() == 1 { if dst.Bounds().Dx() == 1 && dst.Bounds().Dy() == 1 {
dst.Set(0, 0, color.White) dst.Set(0, 0, color.White)
return return

View File

@ -27,8 +27,9 @@ import (
pdfimage "github.com/raff/pdfreader/image" pdfimage "github.com/raff/pdfreader/image"
"github.com/raff/pdfreader/pdfread" "github.com/raff/pdfreader/pdfread"
"github.com/celogeek/go-comic-converter/v2/internal/sortpath" "github.com/celogeek/go-comic-converter/v2/internal/pkg/sortpath"
"github.com/celogeek/go-comic-converter/v2/internal/utils"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/utils"
) )
type task struct { type task struct {
@ -42,7 +43,7 @@ type task struct {
var errNoImagesFound = errors.New("no images found") var errNoImagesFound = errors.New("no images found")
// only accept jpg, png and webp as source file // only accept jpg, png and webp as source file
func (e *EPUBImageProcessor) isSupportedImage(path string) bool { func (e EPUBImageProcessor) isSupportedImage(path string) bool {
switch strings.ToLower(filepath.Ext(path)) { switch strings.ToLower(filepath.Ext(path)) {
case ".jpg", ".jpeg", ".png", ".webp", ".tiff": case ".jpg", ".jpeg", ".png", ".webp", ".tiff":
{ {
@ -53,7 +54,7 @@ func (e *EPUBImageProcessor) isSupportedImage(path string) bool {
} }
// Load images from input // Load images from input
func (e *EPUBImageProcessor) load() (totalImages int, output chan *task, err error) { func (e EPUBImageProcessor) load() (totalImages int, output chan task, err error) {
fi, err := os.Stat(e.Input) fi, err := os.Stat(e.Input)
if err != nil { if err != nil {
return return
@ -77,7 +78,7 @@ func (e *EPUBImageProcessor) load() (totalImages int, output chan *task, err err
} }
} }
func (e *EPUBImageProcessor) corruptedImage(path, name string) image.Image { func (e EPUBImageProcessor) corruptedImage(path, name string) image.Image {
var w, h float64 = 1200, 1920 var w, h float64 = 1200, 1920
f, _ := truetype.Parse(gomonobold.TTF) f, _ := truetype.Parse(gomonobold.TTF)
face := truetype.NewFace(f, &truetype.Options{Size: 64, DPI: 72}) face := truetype.NewFace(f, &truetype.Options{Size: 64, DPI: 72})
@ -101,7 +102,7 @@ func (e *EPUBImageProcessor) corruptedImage(path, name string) image.Image {
} }
// load a directory of images // load a directory of images
func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *task, err error) { func (e EPUBImageProcessor) loadDir() (totalImages int, output chan task, err error) {
images := make([]string, 0) images := make([]string, 0)
input := filepath.Clean(e.Input) input := filepath.Clean(e.Input)
@ -133,16 +134,16 @@ func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *task, err
Id int Id int
Path string Path string
} }
jobs := make(chan *job) jobs := make(chan job)
go func() { go func() {
defer close(jobs) defer close(jobs)
for i, path := range images { for i, path := range images {
jobs <- &job{i, path} jobs <- job{i, path}
} }
}() }()
// read in parallel and get an image // read in parallel and get an image
output = make(chan *task, e.Workers) output = make(chan task, e.Workers)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
for range e.WorkersRatio(50) { for range e.WorkersRatio(50) {
wg.Add(1) wg.Add(1)
@ -169,7 +170,7 @@ func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *task, err
if err != nil { if err != nil {
img = e.corruptedImage(p, fn) img = e.corruptedImage(p, fn)
} }
output <- &task{ output <- task{
Id: job.Id, Id: job.Id,
Image: img, Image: img,
Path: p, Path: p,
@ -190,7 +191,7 @@ func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *task, err
} }
// load a zip file that include images // load a zip file that include images
func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err error) { func (e EPUBImageProcessor) loadCbz() (totalImages int, output chan task, err error) {
r, err := zip.OpenReader(e.Input) r, err := zip.OpenReader(e.Input)
if err != nil { if err != nil {
return return
@ -226,15 +227,15 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err
Id int Id int
F *zip.File F *zip.File
} }
jobs := make(chan *job) jobs := make(chan job)
go func() { go func() {
defer close(jobs) defer close(jobs)
for _, img := range images { for _, img := range images {
jobs <- &job{indexedNames[img.Name], img} jobs <- job{indexedNames[img.Name], img}
} }
}() }()
output = make(chan *task, e.Workers) output = make(chan task, e.Workers)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
for range e.WorkersRatio(50) { for range e.WorkersRatio(50) {
wg.Add(1) wg.Add(1)
@ -256,7 +257,7 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err
if err != nil { if err != nil {
img = e.corruptedImage(p, fn) img = e.corruptedImage(p, fn)
} }
output <- &task{ output <- task{
Id: job.Id, Id: job.Id,
Image: img, Image: img,
Path: p, Path: p,
@ -276,7 +277,7 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err
} }
// load a rar file that include images // load a rar file that include images
func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err error) { func (e EPUBImageProcessor) loadCbr() (totalImages int, output chan task, err error) {
var isSolid bool var isSolid bool
files, err := rardecode.List(e.Input) files, err := rardecode.List(e.Input)
if err != nil { if err != nil {
@ -312,7 +313,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
Open func() (io.ReadCloser, error) Open func() (io.ReadCloser, error)
} }
jobs := make(chan *job) jobs := make(chan job)
go func() { go func() {
defer close(jobs) defer close(jobs)
if isSolid && !e.Dry { if isSolid && !e.Dry {
@ -340,7 +341,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
utils.Printf("\nerror processing image %s: %s\n", f.Name, rerr) utils.Printf("\nerror processing image %s: %s\n", f.Name, rerr)
os.Exit(1) os.Exit(1)
} }
jobs <- &job{i, f.Name, func() (io.ReadCloser, error) { jobs <- job{i, f.Name, func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(b.Bytes())), nil return io.NopCloser(bytes.NewReader(b.Bytes())), nil
}} }}
} }
@ -348,14 +349,14 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
} else { } else {
for _, img := range files { for _, img := range files {
if i, ok := indexedNames[img.Name]; ok { if i, ok := indexedNames[img.Name]; ok {
jobs <- &job{i, img.Name, img.Open} jobs <- job{i, img.Name, img.Open}
} }
} }
} }
}() }()
// send file to the queue // send file to the queue
output = make(chan *task, e.Workers) output = make(chan task, e.Workers)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
for range e.WorkersRatio(50) { for range e.WorkersRatio(50) {
wg.Add(1) wg.Add(1)
@ -377,7 +378,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
if err != nil { if err != nil {
img = e.corruptedImage(p, fn) img = e.corruptedImage(p, fn)
} }
output <- &task{ output <- task{
Id: job.Id, Id: job.Id,
Image: img, Image: img,
Path: p, Path: p,
@ -395,7 +396,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
} }
// extract image from a pdf // extract image from a pdf
func (e *EPUBImageProcessor) loadPdf() (totalImages int, output chan *task, err error) { func (e EPUBImageProcessor) loadPdf() (totalImages int, output chan task, err error) {
pdf := pdfread.Load(e.Input) pdf := pdfread.Load(e.Input)
if pdf == nil { if pdf == nil {
err = fmt.Errorf("can't read pdf") err = fmt.Errorf("can't read pdf")
@ -404,7 +405,7 @@ func (e *EPUBImageProcessor) loadPdf() (totalImages int, output chan *task, err
totalImages = len(pdf.Pages()) totalImages = len(pdf.Pages())
pageFmt := fmt.Sprintf("page %%0%dd", len(fmt.Sprintf("%d", totalImages))) pageFmt := fmt.Sprintf("page %%0%dd", len(fmt.Sprintf("%d", totalImages)))
output = make(chan *task) output = make(chan task)
go func() { go func() {
defer close(output) defer close(output)
defer pdf.Close() defer pdf.Close()
@ -419,7 +420,7 @@ func (e *EPUBImageProcessor) loadPdf() (totalImages int, output chan *task, err
if err != nil { if err != nil {
img = e.corruptedImage("", name) img = e.corruptedImage("", name)
} }
output <- &task{ output <- task{
Id: i, Id: i,
Image: img, Image: img,
Path: "", Path: "",

View File

@ -11,25 +11,25 @@ import (
"github.com/disintegration/gift" "github.com/disintegration/gift"
epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubimage"
epubimagefilters "github.com/celogeek/go-comic-converter/v2/internal/epub/imagefilters" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubimagefilters"
epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epuboptions"
epubprogress "github.com/celogeek/go-comic-converter/v2/internal/epub/progress" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubprogress"
epubzip "github.com/celogeek/go-comic-converter/v2/internal/epub/zip" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubzip"
"github.com/celogeek/go-comic-converter/v2/internal/utils" "github.com/celogeek/go-comic-converter/v2/internal/pkg/utils"
) )
type EPUBImageProcessor struct { type EPUBImageProcessor struct {
*epuboptions.Options epuboptions.EPUBOptions
} }
func New(o *epuboptions.Options) *EPUBImageProcessor { func New(o epuboptions.EPUBOptions) EPUBImageProcessor {
return &EPUBImageProcessor{o} return EPUBImageProcessor{o}
} }
// Load extract and convert images // Load extract and convert images
func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { func (e EPUBImageProcessor) Load() (images []epubimage.EPUBImage, err error) {
images = make([]*epubimage.Image, 0) images = make([]epubimage.EPUBImage, 0)
imageCount, imageInput, err := e.load() imageCount, imageInput, err := e.load()
if err != nil { if err != nil {
return nil, err return nil, err
@ -38,7 +38,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
// dry run, skip conversion // dry run, skip conversion
if e.Dry { if e.Dry {
for img := range imageInput { for img := range imageInput {
images = append(images, &epubimage.Image{ images = append(images, epubimage.EPUBImage{
Id: img.Id, Id: img.Id,
Path: img.Path, Path: img.Path,
Name: img.Name, Name: img.Name,
@ -49,7 +49,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
return images, nil return images, nil
} }
imageOutput := make(chan *epubimage.Image) imageOutput := make(chan epubimage.EPUBImage)
// processing // processing
bar := epubprogress.New(epubprogress.Options{ bar := epubprogress.New(epubprogress.Options{
@ -82,7 +82,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
// do not keep double page if requested // do not keep double page if requested
if !(img.DoublePage && input.Id > 0 && if !(img.DoublePage && input.Id > 0 &&
e.Options.Image.AutoSplitDoublePage && !e.Options.Image.KeepDoublePageIfSplit) { e.EPUBOptions.Image.AutoSplitDoublePage && !e.EPUBOptions.Image.KeepDoublePageIfSplit) {
if err = imgStorage.Add(img.EPUBImgPath(), img.Raw, e.Image.Quality); err != nil { if err = imgStorage.Add(img.EPUBImgPath(), img.Raw, e.Image.Quality); err != nil {
_ = bar.Close() _ = bar.Close()
utils.Printf("error with %s: %s", input.Name, err) utils.Printf("error with %s: %s", input.Name, err)
@ -140,8 +140,8 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
return images, nil return images, nil
} }
func (e *EPUBImageProcessor) createImage(src image.Image, r image.Rectangle) draw.Image { func (e EPUBImageProcessor) createImage(src image.Image, r image.Rectangle) draw.Image {
if e.Options.Image.GrayScale { if e.EPUBOptions.Image.GrayScale {
return image.NewGray(r) return image.NewGray(r)
} }
@ -173,7 +173,7 @@ func (e *EPUBImageProcessor) createImage(src image.Image, r image.Rectangle) dra
// transform image into 1 or 3 images // transform image into 1 or 3 images
// only doublepage with autosplit has 3 versions // only doublepage with autosplit has 3 versions
func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) *epubimage.Image { func (e EPUBImageProcessor) transformImage(input task, part int, right bool) epubimage.EPUBImage {
g := gift.New() g := gift.New()
src := input.Image src := input.Image
srcBounds := src.Bounds() srcBounds := src.Bounds()
@ -262,7 +262,7 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) *
dst := e.createImage(src, g.Bounds(src.Bounds())) dst := e.createImage(src, g.Bounds(src.Bounds()))
g.Draw(dst, src) g.Draw(dst, src)
return &epubimage.Image{ return epubimage.EPUBImage{
Id: input.Id, Id: input.Id,
Part: part, Part: part,
Raw: dst, Raw: dst,
@ -290,7 +290,7 @@ type CoverTitleDataOptions struct {
BorderSize int BorderSize int
} }
func (e *EPUBImageProcessor) Cover16LevelOfGray(bounds image.Rectangle) draw.Image { func (e EPUBImageProcessor) Cover16LevelOfGray(bounds image.Rectangle) draw.Image {
return image.NewPaletted(bounds, color.Palette{ return image.NewPaletted(bounds, color.Palette{
color.Gray{}, color.Gray{},
color.Gray{Y: 0x11}, color.Gray{Y: 0x11},
@ -312,7 +312,7 @@ func (e *EPUBImageProcessor) Cover16LevelOfGray(bounds image.Rectangle) draw.Ima
} }
// CoverTitleData create a title page with the cover // CoverTitleData create a title page with the cover
func (e *EPUBImageProcessor) CoverTitleData(o *CoverTitleDataOptions) (*epubzip.ZipImage, error) { func (e EPUBImageProcessor) CoverTitleData(o CoverTitleDataOptions) (epubzip.Image, error) {
// Create a blur version of the cover // Create a blur version of the cover
g := gift.New(epubimagefilters.CoverTitle(o.Text, o.Align, o.PctWidth, o.PctMargin, o.MaxFontSize, o.BorderSize)) g := gift.New(epubimagefilters.CoverTitle(o.Text, o.Align, o.PctWidth, o.PctMargin, o.MaxFontSize, o.BorderSize))
var dst draw.Image var dst draw.Image

View File

@ -0,0 +1,5 @@
package epuboptions
type Color struct {
Foreground, Background string
}

View File

@ -0,0 +1,8 @@
package epuboptions
type Crop struct {
Enabled bool
Left, Up, Right, Bottom int
Limit int
SkipIfLimitReached bool
}

View File

@ -0,0 +1,33 @@
// Package epuboptions EPUBOptions for EPUB creation.
package epuboptions
import "fmt"
type EPUBOptions struct {
Input string
Output string
Title string
TitlePage int
Author string
LimitMb int
StripFirstDirectoryFromToc bool
Dry bool
DryVerbose bool
SortPathMode int
Quiet bool
Json bool
Workers int
Image Image
}
func (o EPUBOptions) WorkersRatio(pct int) (nbWorkers int) {
nbWorkers = o.Workers * pct / 100
if nbWorkers < 1 {
nbWorkers = 1
}
return
}
func (o EPUBOptions) ImgStorage() string {
return fmt.Sprintf("%s.tmp", o.Output)
}

View File

@ -0,0 +1,22 @@
package epuboptions
type Image struct {
Crop Crop
Quality int
Brightness int
Contrast int
AutoContrast bool
AutoRotate bool
AutoSplitDoublePage bool
KeepDoublePageIfSplit bool
KeepSplitDoublePageAspect bool
NoBlankImage bool
Manga bool
HasCover bool
View View
GrayScale bool
GrayScaleMode int
Resize bool
Format string
AppleBookCompatibility bool
}

View File

@ -0,0 +1,8 @@
package epuboptions
type View struct {
Width, Height int
AspectRatio float64
PortraitOnly bool
Color Color
}

View File

@ -2,13 +2,14 @@
package epubprogress package epubprogress
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "os"
"time" "time"
"github.com/schollz/progressbar/v3" "github.com/schollz/progressbar/v3"
"github.com/celogeek/go-comic-converter/v2/internal/utils" "github.com/celogeek/go-comic-converter/v2/internal/pkg/utils"
) )
type Options struct { type Options struct {
@ -20,18 +21,21 @@ type Options struct {
TotalJob int TotalJob int
} }
type EpubProgress interface { type EPUBProgress interface {
Add(num int) error Add(num int) error
Close() error Close() error
} }
func New(o Options) EpubProgress { func New(o Options) EPUBProgress {
if o.Quiet { if o.Quiet {
return progressbar.DefaultSilent(int64(o.Max)) return progressbar.DefaultSilent(int64(o.Max))
} }
if o.Json { if o.Json {
return newEpubProgressJson(o) return &jsonprogress{
o: o,
e: json.NewEncoder(os.Stdout),
}
} }
fmtJob := fmt.Sprintf("%%0%dd", len(fmt.Sprint(o.TotalJob))) fmtJob := fmt.Sprintf("%%0%dd", len(fmt.Sprint(o.TotalJob)))

View File

@ -2,28 +2,20 @@ package epubprogress
import ( import (
"encoding/json" "encoding/json"
"os"
) )
type Json struct { type jsonprogress struct {
o Options o Options
e *json.Encoder e *json.Encoder
current int current int
} }
func newEpubProgressJson(o Options) EpubProgress { func (p *jsonprogress) Add(num int) error {
return &Json{
o: o,
e: json.NewEncoder(os.Stdout),
}
}
func (p *Json) Add(num int) error {
p.current += num p.current += num
return p.e.Encode(map[string]any{ return p.e.Encode(map[string]any{
"type": "progress", "type": "epubprogress",
"data": map[string]any{ "data": map[string]any{
"progress": map[string]any{ "epubprogress": map[string]any{
"current": p.current, "current": p.current,
"total": p.o.Max, "total": p.o.Max,
}, },
@ -36,6 +28,6 @@ func (p *Json) Add(num int) error {
}) })
} }
func (p *Json) Close() error { func (p *jsonprogress) Close() error {
return nil return nil
} }

View File

@ -0,0 +1,6 @@
package epubtemplates
import _ "embed"
//go:embed "applebooks.xml.tmpl"
var AppleBooks string

View File

@ -0,0 +1,7 @@
// Package epubtemplates Templates use to create xml files of the EPUB.
package epubtemplates
import _ "embed"
//go:embed "blank.xhtml.tmpl"
var Blank string

View File

@ -0,0 +1,6 @@
package epubtemplates
import _ "embed"
//go:embed "container.xml.tmpl"
var Container string

View File

@ -5,20 +5,20 @@ import (
"github.com/beevik/etree" "github.com/beevik/etree"
epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubimage"
epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epuboptions"
) )
type ContentOptions struct { type Content struct {
Title string Title string
HasTitlePage bool HasTitlePage bool
UID string UID string
Author string Author string
Publisher string Publisher string
UpdatedAt string UpdatedAt string
ImageOptions *epuboptions.Image ImageOptions epuboptions.Image
Cover *epubimage.Image Cover epubimage.EPUBImage
Images []*epubimage.Image Images []epubimage.EPUBImage
Current int Current int
Total int Total int
} }
@ -31,10 +31,10 @@ type tag struct {
value string value string
} }
// Content create the content file // Get create the content file
// //
//goland:noinspection HttpUrlsUsage,HttpUrlsUsage,HttpUrlsUsage,HttpUrlsUsage //goland:noinspection HttpUrlsUsage,HttpUrlsUsage,HttpUrlsUsage,HttpUrlsUsage
func Content(o *ContentOptions) string { func (o Content) String() string {
doc := etree.NewDocument() doc := etree.NewDocument()
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`) doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
@ -44,8 +44,8 @@ func Content(o *ContentOptions) string {
pkg.CreateAttr("version", "3.0") pkg.CreateAttr("version", "3.0")
pkg.CreateAttr("prefix", "rendition: http://www.idpf.org/vocab/rendition/#") pkg.CreateAttr("prefix", "rendition: http://www.idpf.org/vocab/rendition/#")
addToElement := func(elm *etree.Element, meth func(o *ContentOptions) []tag) { addToElement := func(elm *etree.Element, meth func() []tag) {
for _, p := range meth(o) { for _, p := range meth() {
meta := elm.CreateElement(p.name) meta := elm.CreateElement(p.name)
for k, v := range p.attrs { for k, v := range p.attrs {
meta.CreateAttr(k, v) meta.CreateAttr(k, v)
@ -60,10 +60,10 @@ func Content(o *ContentOptions) string {
metadata := pkg.CreateElement("metadata") metadata := pkg.CreateElement("metadata")
metadata.CreateAttr("xmlns:dc", "http://purl.org/dc/elements/1.1/") metadata.CreateAttr("xmlns:dc", "http://purl.org/dc/elements/1.1/")
metadata.CreateAttr("xmlns:opf", "http://www.idpf.org/2007/opf") metadata.CreateAttr("xmlns:opf", "http://www.idpf.org/2007/opf")
addToElement(metadata, getMeta) addToElement(metadata, o.getMeta)
manifest := pkg.CreateElement("manifest") manifest := pkg.CreateElement("manifest")
addToElement(manifest, getManifest) addToElement(manifest, o.getManifest)
spine := pkg.CreateElement("spine") spine := pkg.CreateElement("spine")
if o.ImageOptions.Manga { if o.ImageOptions.Manga {
@ -73,13 +73,13 @@ func Content(o *ContentOptions) string {
} }
if o.ImageOptions.View.PortraitOnly { if o.ImageOptions.View.PortraitOnly {
addToElement(spine, getSpinePortrait) addToElement(spine, o.getSpinePortrait)
} else { } else {
addToElement(spine, getSpineAuto) addToElement(spine, o.getSpineAuto)
} }
guide := pkg.CreateElement("guide") guide := pkg.CreateElement("guide")
addToElement(guide, getGuide) addToElement(guide, o.getGuide)
doc.Indent(2) doc.Indent(2)
r, _ := doc.WriteToString() r, _ := doc.WriteToString()
@ -88,7 +88,7 @@ func Content(o *ContentOptions) string {
} }
// metadata part of the content // metadata part of the content
func getMeta(o *ContentOptions) []tag { func (o Content) getMeta() []tag {
metas := []tag{ metas := []tag{
{"meta", tagAttrs{"property": "dcterms:modified"}, o.UpdatedAt}, {"meta", tagAttrs{"property": "dcterms:modified"}, o.UpdatedAt},
{"meta", tagAttrs{"property": "schema:accessMode"}, "visual"}, {"meta", tagAttrs{"property": "schema:accessMode"}, "visual"},
@ -141,9 +141,9 @@ func getMeta(o *ContentOptions) []tag {
return metas return metas
} }
func getManifest(o *ContentOptions) []tag { func (o Content) getManifest() []tag {
var imageTags, pageTags, spaceTags []tag var imageTags, pageTags, spaceTags []tag
addTag := func(img *epubimage.Image, withSpace bool) { addTag := func(img epubimage.EPUBImage, withSpace bool) {
imageTags = append(imageTags, imageTags = append(imageTags,
tag{"item", tagAttrs{"id": img.ImgKey(), "href": img.ImgPath(), "media-type": fmt.Sprintf("image/%s", o.ImageOptions.Format)}, ""}, tag{"item", tagAttrs{"id": img.ImgKey(), "href": img.ImgPath(), "media-type": fmt.Sprintf("image/%s", o.ImageOptions.Format)}, ""},
) )
@ -193,7 +193,7 @@ func getManifest(o *ContentOptions) []tag {
} }
// spine part of the content // spine part of the content
func getSpineAuto(o *ContentOptions) []tag { func (o Content) getSpineAuto() []tag {
isOnTheRight := !o.ImageOptions.Manga isOnTheRight := !o.ImageOptions.Manga
if o.ImageOptions.AppleBookCompatibility { if o.ImageOptions.AppleBookCompatibility {
isOnTheRight = !isOnTheRight isOnTheRight = !isOnTheRight
@ -255,7 +255,7 @@ func getSpineAuto(o *ContentOptions) []tag {
return spine return spine
} }
func getSpinePortrait(o *ContentOptions) []tag { func (o Content) getSpinePortrait() []tag {
var spine []tag var spine []tag
if o.HasTitlePage { if o.HasTitlePage {
spine = append(spine, spine = append(spine,
@ -273,7 +273,7 @@ func getSpinePortrait(o *ContentOptions) []tag {
} }
// getGuide Section guide of the content // getGuide Section guide of the content
func getGuide(o *ContentOptions) []tag { func (o Content) getGuide() []tag {
return []tag{ return []tag{
{"reference", tagAttrs{"type": "cover", "title": "cover", "href": "Text/cover.xhtml"}, ""}, {"reference", tagAttrs{"type": "cover", "title": "cover", "href": "Text/cover.xhtml"}, ""},
{"reference", tagAttrs{"type": "text", "title": "content", "href": o.Images[0].PagePath()}, ""}, {"reference", tagAttrs{"type": "text", "title": "content", "href": o.Images[0].PagePath()}, ""},

View File

@ -0,0 +1,6 @@
package epubtemplates
import _ "embed"
//go:embed "style.css.tmpl"
var Style string

View File

@ -0,0 +1,6 @@
package epubtemplates
import _ "embed"
//go:embed "text.xhtml.tmpl"
var Text string

View File

@ -6,13 +6,13 @@ import (
"github.com/beevik/etree" "github.com/beevik/etree"
epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epubimage"
) )
// Toc create toc // Toc create toc
// //
//goland:noinspection HttpUrlsUsage //goland:noinspection HttpUrlsUsage
func Toc(title string, hasTitle bool, stripFirstDirectoryFromToc bool, images []*epubimage.Image) string { func Toc(title string, hasTitle bool, stripFirstDirectoryFromToc bool, images []epubimage.EPUBImage) string {
doc := etree.NewDocument() doc := etree.NewDocument()
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`) doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
doc.CreateDirective("DOCTYPE html") doc.CreateDirective("DOCTYPE html")

View File

@ -23,7 +23,7 @@ import (
"strings" "strings"
) )
type Tree struct { type EPUBTree struct {
nodes map[string]*Node nodes map[string]*Node
} }
@ -33,19 +33,19 @@ type Node struct {
} }
// New initialize tree with a root node // New initialize tree with a root node
func New() *Tree { func New() *EPUBTree {
return &Tree{map[string]*Node{ return &EPUBTree{map[string]*Node{
".": {".", []*Node{}}, ".": {".", []*Node{}},
}} }}
} }
// Root root node // Root root node
func (n *Tree) Root() *Node { func (n *EPUBTree) Root() *Node {
return n.nodes["."] return n.nodes["."]
} }
// Add the filename to the tree // Add the filename to the tree
func (n *Tree) Add(filename string) { func (n *EPUBTree) Add(filename string) {
cn := n.Root() cn := n.Root()
cp := "" cp := ""
for _, p := range strings.Split(filepath.Clean(filename), string(filepath.Separator)) { for _, p := range strings.Split(filepath.Clean(filename), string(filepath.Separator)) {

View File

@ -17,17 +17,17 @@ type EPUBZip struct {
} }
// New create a new EPUB // New create a new EPUB
func New(path string) (*EPUBZip, error) { func New(path string) (EPUBZip, error) {
w, err := os.Create(path) w, err := os.Create(path)
if err != nil { if err != nil {
return nil, err return EPUBZip{}, err
} }
wz := zip.NewWriter(w) wz := zip.NewWriter(w)
return &EPUBZip{w, wz}, nil return EPUBZip{w, wz}, nil
} }
// Close compress pipe and file. // Close compress pipe and file.
func (e *EPUBZip) Close() error { func (e EPUBZip) Close() error {
if err := e.wz.Close(); err != nil { if err := e.wz.Close(); err != nil {
return err return err
} }
@ -37,10 +37,10 @@ func (e *EPUBZip) Close() error {
// WriteMagic Write mimetype, in a very specific way. // WriteMagic Write mimetype, in a very specific way.
// //
// This will be valid with epubcheck tools. // This will be valid with epubcheck tools.
func (e *EPUBZip) WriteMagic() error { func (e EPUBZip) WriteMagic() error {
t := time.Now().UTC() t := time.Now().UTC()
//goland:noinspection GoDeprecation //goland:noinspection GoDeprecation
fh := &zip.FileHeader{ fh := zip.FileHeader{
Name: "mimetype", Name: "mimetype",
Method: zip.Store, Method: zip.Store,
Modified: t, Modified: t,
@ -53,7 +53,7 @@ func (e *EPUBZip) WriteMagic() error {
fh.CreatorVersion = fh.CreatorVersion&0xff00 | 20 // preserve compatibility byte fh.CreatorVersion = fh.CreatorVersion&0xff00 | 20 // preserve compatibility byte
fh.ReaderVersion = 20 fh.ReaderVersion = 20
fh.SetMode(0600) fh.SetMode(0600)
m, err := e.wz.CreateRaw(fh) m, err := e.wz.CreateRaw(&fh)
if err != nil { if err != nil {
return err return err
@ -62,12 +62,12 @@ func (e *EPUBZip) WriteMagic() error {
return err return err
} }
func (e *EPUBZip) Copy(fz *zip.File) error { func (e EPUBZip) Copy(fz *zip.File) error {
return e.wz.Copy(fz) return e.wz.Copy(fz)
} }
// WriteRaw Write image. They are already compressed, so we write them down directly. // WriteRaw Write image. They are already compressed, so we write them down directly.
func (e *EPUBZip) WriteRaw(raw *ZipImage) error { func (e EPUBZip) WriteRaw(raw Image) error {
m, err := e.wz.CreateRaw(raw.Header) m, err := e.wz.CreateRaw(raw.Header)
if err != nil { if err != nil {
return err return err
@ -77,7 +77,7 @@ func (e *EPUBZip) WriteRaw(raw *ZipImage) error {
} }
// WriteContent Write file. Compressed it using deflate. // WriteContent Write file. Compressed it using deflate.
func (e *EPUBZip) WriteContent(file string, content []byte) error { func (e EPUBZip) WriteContent(file string, content []byte) error {
m, err := e.wz.CreateHeader(&zip.FileHeader{ m, err := e.wz.CreateHeader(&zip.FileHeader{
Name: file, Name: file,
Modified: time.Now(), Modified: time.Now(),

View File

@ -12,13 +12,13 @@ import (
"time" "time"
) )
type ZipImage struct { type Image struct {
Header *zip.FileHeader Header *zip.FileHeader
Data []byte Data []byte
} }
// CompressImage create gzip encoded jpeg // CompressImage create gzip encoded jpeg
func CompressImage(filename string, format string, img image.Image, quality int) (*ZipImage, error) { func CompressImage(filename string, format string, img image.Image, quality int) (Image, error) {
var ( var (
data, cdata bytes.Buffer data, cdata bytes.Buffer
err error err error
@ -33,27 +33,27 @@ func CompressImage(filename string, format string, img image.Image, quality int)
err = fmt.Errorf("unknown format %q", format) err = fmt.Errorf("unknown format %q", format)
} }
if err != nil { if err != nil {
return nil, err return Image{}, err
} }
wcdata, err := flate.NewWriter(&cdata, flate.BestCompression) wcdata, err := flate.NewWriter(&cdata, flate.BestCompression)
if err != nil { if err != nil {
return nil, err return Image{}, err
} }
_, err = wcdata.Write(data.Bytes()) _, err = wcdata.Write(data.Bytes())
if err != nil { if err != nil {
return nil, err return Image{}, err
} }
err = wcdata.Close() err = wcdata.Close()
if err != nil { if err != nil {
return nil, err return Image{}, err
} }
t := time.Now() t := time.Now()
//goland:noinspection GoDeprecation //goland:noinspection GoDeprecation
return &ZipImage{ return Image{
&zip.FileHeader{ &zip.FileHeader{
Name: filename, Name: filename,
CompressedSize64: uint64(cdata.Len()), CompressedSize64: uint64(cdata.Len()),

View File

@ -0,0 +1,53 @@
package epubzip
import (
"archive/zip"
"os"
)
type StorageImageReader struct {
filename string
fh *os.File
fz *zip.Reader
files map[string]*zip.File
}
func NewStorageImageReader(filename string) (StorageImageReader, error) {
fh, err := os.Open(filename)
if err != nil {
return StorageImageReader{}, err
}
s, err := fh.Stat()
if err != nil {
return StorageImageReader{}, err
}
fz, err := zip.NewReader(fh, s.Size())
if err != nil {
return StorageImageReader{}, err
}
files := map[string]*zip.File{}
for _, z := range fz.File {
files[z.Name] = z
}
return StorageImageReader{filename, fh, fz, files}, nil
}
func (e StorageImageReader) Get(filename string) *zip.File {
return e.files[filename]
}
func (e StorageImageReader) Size(filename string) uint64 {
if img, ok := e.files[filename]; ok {
return img.CompressedSize64 + 30 + uint64(len(img.Name))
}
return 0
}
func (e StorageImageReader) Close() error {
return e.fh.Close()
}
func (e StorageImageReader) Remove() error {
return os.Remove(e.filename)
}

View File

@ -0,0 +1,52 @@
package epubzip
import (
"archive/zip"
"image"
"os"
"sync"
)
type StorageImageWriter struct {
fh *os.File
fz *zip.Writer
format string
mut *sync.Mutex
}
func NewStorageImageWriter(filename string, format string) (StorageImageWriter, error) {
fh, err := os.Create(filename)
if err != nil {
return StorageImageWriter{}, err
}
fz := zip.NewWriter(fh)
return StorageImageWriter{fh, fz, format, &sync.Mutex{}}, nil
}
func (e StorageImageWriter) Close() error {
if err := e.fz.Close(); err != nil {
_ = e.fh.Close()
return err
}
return e.fh.Close()
}
func (e StorageImageWriter) Add(filename string, img image.Image, quality int) error {
zipImage, err := CompressImage(filename, e.format, img, quality)
if err != nil {
return err
}
e.mut.Lock()
defer e.mut.Unlock()
fh, err := e.fz.CreateRaw(zipImage.Header)
if err != nil {
return err
}
_, err = fh.Write(zipImage.Data)
if err != nil {
return err
}
return nil
}

142
main.go
View File

@ -14,10 +14,10 @@ import (
"github.com/tcnksm/go-latest" "github.com/tcnksm/go-latest"
"github.com/celogeek/go-comic-converter/v2/internal/converter" "github.com/celogeek/go-comic-converter/v2/internal/pkg/converter"
"github.com/celogeek/go-comic-converter/v2/internal/epub" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epub"
epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options" "github.com/celogeek/go-comic-converter/v2/internal/pkg/epuboptions"
"github.com/celogeek/go-comic-converter/v2/internal/utils" "github.com/celogeek/go-comic-converter/v2/internal/pkg/utils"
) )
func main() { func main() {
@ -28,25 +28,40 @@ func main() {
cmd.InitParse() cmd.InitParse()
cmd.Parse() cmd.Parse()
if cmd.Options.Version { switch {
bi, ok := debug.ReadBuildInfo() case cmd.Options.Version:
if !ok { version()
utils.Println("failed to fetch current version") case cmd.Options.Save:
os.Exit(1) save(cmd)
} case cmd.Options.Show:
show(cmd)
case cmd.Options.Reset:
reset(cmd)
default:
generate(cmd)
}
githubTag := &latest.GithubTag{ }
Owner: "celogeek",
Repository: "go-comic-converter",
}
v, err := githubTag.Fetch()
if err != nil || len(v.Versions) < 1 {
utils.Println("failed to fetch the latest version")
os.Exit(1)
}
latestVersion := v.Versions[0]
utils.Printf(`go-comic-converter func version() {
bi, ok := debug.ReadBuildInfo()
if !ok {
utils.Println("failed to fetch current version")
os.Exit(1)
}
githubTag := &latest.GithubTag{
Owner: "celogeek",
Repository: "go-comic-converter",
}
v, err := githubTag.Fetch()
if err != nil || len(v.Versions) < 1 {
utils.Println("failed to fetch the latest version")
os.Exit(1)
}
latestVersion := v.Versions[0]
utils.Printf(`go-comic-converter
Path : %s Path : %s
Sum : %s Sum : %s
Version : %s Version : %s
@ -55,47 +70,54 @@ func main() {
To install the latest version: To install the latest version:
$ go install github.com/celogeek/go-comic-converter/v%d@%s $ go install github.com/celogeek/go-comic-converter/v%d@%s
`, `,
bi.Main.Path, bi.Main.Path,
bi.Main.Sum, bi.Main.Sum,
bi.Main.Version, bi.Main.Version,
latestVersion.Original(), latestVersion.Original(),
latestVersion.Segments()[0], latestVersion.Segments()[0],
latestVersion.Original(), latestVersion.Original(),
) )
return }
}
if cmd.Options.Save { func save(cmd *converter.Converter) {
if err := cmd.Options.SaveConfig(); err != nil { if err := cmd.Options.SaveConfig(); err != nil {
cmd.Fatal(err) cmd.Fatal(err)
}
utils.Printf(
"%s%s\n\nSaving to %s\n",
cmd.Options.Header(),
cmd.Options.ShowConfig(),
cmd.Options.FileName(),
)
return
} }
utils.Printf(
"%s%s\n\nSaving to %s\n",
cmd.Options.Header(),
cmd.Options.ShowConfig(),
cmd.Options.FileName(),
)
}
if cmd.Options.Show { func show(cmd *converter.Converter) {
utils.Println(cmd.Options.Header(), cmd.Options.ShowConfig()) utils.Println(cmd.Options.Header(), cmd.Options.ShowConfig())
return }
func reset(cmd *converter.Converter) {
if err := cmd.Options.ResetConfig(); err != nil {
cmd.Fatal(err)
} }
utils.Printf(
if cmd.Options.Reset { "%s%s\n\nReset default to %s\n",
if err := cmd.Options.ResetConfig(); err != nil { cmd.Options.Header(),
cmd.Fatal(err) cmd.Options.ShowConfig(),
} cmd.Options.FileName(),
utils.Printf( )
"%s%s\n\nReset default to %s\n", if err := cmd.Options.ResetConfig(); err != nil {
cmd.Options.Header(), cmd.Fatal(err)
cmd.Options.ShowConfig(),
cmd.Options.FileName(),
)
return
} }
utils.Printf(
"%s%s\n\nReset default to %s\n",
cmd.Options.Header(),
cmd.Options.ShowConfig(),
cmd.Options.FileName(),
)
}
func generate(cmd *converter.Converter) {
if err := cmd.Validate(); err != nil { if err := cmd.Validate(); err != nil {
cmd.Fatal(err) cmd.Fatal(err)
} }
@ -110,7 +132,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
profile := cmd.Options.GetProfile() profile := cmd.Options.GetProfile()
if err := epub.New(&epuboptions.Options{ if err := epub.New(epuboptions.EPUBOptions{
Input: cmd.Options.Input, Input: cmd.Options.Input,
Output: cmd.Options.Output, Output: cmd.Options.Output,
LimitMb: cmd.Options.LimitMb, LimitMb: cmd.Options.LimitMb,
@ -124,8 +146,8 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
DryVerbose: cmd.Options.DryVerbose, DryVerbose: cmd.Options.DryVerbose,
Quiet: cmd.Options.Quiet, Quiet: cmd.Options.Quiet,
Json: cmd.Options.Json, Json: cmd.Options.Json,
Image: &epuboptions.Image{ Image: epuboptions.Image{
Crop: &epuboptions.Crop{ Crop: epuboptions.Crop{
Enabled: cmd.Options.Crop, Enabled: cmd.Options.Crop,
Left: cmd.Options.CropRatioLeft, Left: cmd.Options.CropRatioLeft,
Up: cmd.Options.CropRatioUp, Up: cmd.Options.CropRatioUp,
@ -145,7 +167,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
NoBlankImage: cmd.Options.NoBlankImage, NoBlankImage: cmd.Options.NoBlankImage,
Manga: cmd.Options.Manga, Manga: cmd.Options.Manga,
HasCover: cmd.Options.HasCover, HasCover: cmd.Options.HasCover,
View: &epuboptions.View{ View: epuboptions.View{
Width: profile.Width, Width: profile.Width,
Height: profile.Height, Height: profile.Height,
AspectRatio: cmd.Options.AspectRatio, AspectRatio: cmd.Options.AspectRatio,