Compare commits

..

No commits in common. "93294ba1f926011222ed9cd3546a5085e3606871" and "2ce29d62ecacc6c49085c59a7af054205de7b9ef" have entirely different histories.

46 changed files with 486 additions and 517 deletions

8
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0
github.com/schollz/progressbar/v3 v3.14.2
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e
golang.org/x/image v0.16.0
golang.org/x/image v0.15.0
gopkg.in/yaml.v3 v3.0.1
)
@ -23,7 +23,7 @@ require (
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.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/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2oeKxpIUmtiDV5sn71VgeQgg6vcE7k=
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM=
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,14 +10,14 @@ import (
// AutoContrast Automatically improve contrast
func AutoContrast() gift.Filter {
return autocontrast{}
return &autocontrast{}
}
type autocontrast struct {
}
// 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{}
for x := src.Bounds().Min.X; x < src.Bounds().Max.X; x++ {
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
func (f autocontrast) cap(v float32) float32 {
func (f *autocontrast) cap(v float32) float32 {
if v < 0 {
return 0
}
@ -55,12 +55,12 @@ func (f autocontrast) cap(v float32) float32 {
}
// power of 2 for float32
func (f autocontrast) pow2(v float32) float32 {
func (f *autocontrast) pow2(v float32) float32 {
return v * v
}
// 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
colorMean := f.mean(src)
@ -84,7 +84,7 @@ func (f autocontrast) Draw(dst draw.Image, src image.Image, options *gift.Option
}
// 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
return
}

View File

@ -13,7 +13,7 @@ import (
// CoverTitle Create a title with the cover image
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 {
@ -26,12 +26,12 @@ type coverTitle struct {
}
// 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
}
// 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)
if p.title == "" {
return

View File

@ -11,14 +11,14 @@ import (
//
// This will cut in the middle of the page.
func CropSplitDoublePage(right bool) gift.Filter {
return cropSplitDoublePage{right}
return &cropSplitDoublePage{right}
}
type cropSplitDoublePage struct {
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 {
dstBounds = image.Rect(
srcBounds.Max.X/2, srcBounds.Min.Y,
@ -33,6 +33,6 @@ func (p cropSplitDoublePage) Bounds(srcBounds image.Rectangle) (dstBounds image.
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)
}

View File

@ -12,13 +12,13 @@ import (
//
// An image 0x0 is not a valid image, and failed to read.
func Pixel() gift.Filter {
return pixel{}
return &pixel{}
}
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 {
dstBounds = image.Rect(0, 0, 1, 1)
} else {
@ -27,7 +27,7 @@ func (p pixel) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
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 {
dst.Set(0, 0, color.White)
return

View File

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

View File

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

View File

@ -0,0 +1,72 @@
// 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

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

View File

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

View File

@ -0,0 +1,21 @@
// 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

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

View File

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

View File

@ -23,7 +23,7 @@ import (
"strings"
)
type EPUBTree struct {
type Tree struct {
nodes map[string]*Node
}
@ -33,19 +33,19 @@ type Node struct {
}
// New initialize tree with a root node
func New() *EPUBTree {
return &EPUBTree{map[string]*Node{
func New() *Tree {
return &Tree{map[string]*Node{
".": {".", []*Node{}},
}}
}
// Root root node
func (n *EPUBTree) Root() *Node {
func (n *Tree) Root() *Node {
return n.nodes["."]
}
// Add the filename to the tree
func (n *EPUBTree) Add(filename string) {
func (n *Tree) Add(filename string) {
cn := n.Root()
cp := ""
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
func New(path string) (EPUBZip, error) {
func New(path string) (*EPUBZip, error) {
w, err := os.Create(path)
if err != nil {
return EPUBZip{}, err
return nil, err
}
wz := zip.NewWriter(w)
return EPUBZip{w, wz}, nil
return &EPUBZip{w, wz}, nil
}
// Close compress pipe and file.
func (e EPUBZip) Close() error {
func (e *EPUBZip) Close() error {
if err := e.wz.Close(); err != nil {
return err
}
@ -37,10 +37,10 @@ func (e EPUBZip) Close() error {
// WriteMagic Write mimetype, in a very specific way.
//
// This will be valid with epubcheck tools.
func (e EPUBZip) WriteMagic() error {
func (e *EPUBZip) WriteMagic() error {
t := time.Now().UTC()
//goland:noinspection GoDeprecation
fh := zip.FileHeader{
fh := &zip.FileHeader{
Name: "mimetype",
Method: zip.Store,
Modified: t,
@ -53,7 +53,7 @@ func (e EPUBZip) WriteMagic() error {
fh.CreatorVersion = fh.CreatorVersion&0xff00 | 20 // preserve compatibility byte
fh.ReaderVersion = 20
fh.SetMode(0600)
m, err := e.wz.CreateRaw(&fh)
m, err := e.wz.CreateRaw(fh)
if err != nil {
return err
@ -62,12 +62,12 @@ func (e EPUBZip) WriteMagic() error {
return err
}
func (e EPUBZip) Copy(fz *zip.File) error {
func (e *EPUBZip) Copy(fz *zip.File) error {
return e.wz.Copy(fz)
}
// WriteRaw Write image. They are already compressed, so we write them down directly.
func (e EPUBZip) WriteRaw(raw Image) error {
func (e *EPUBZip) WriteRaw(raw *ZipImage) error {
m, err := e.wz.CreateRaw(raw.Header)
if err != nil {
return err
@ -77,7 +77,7 @@ func (e EPUBZip) WriteRaw(raw Image) error {
}
// 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{
Name: file,
Modified: time.Now(),

View File

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

View File

@ -0,0 +1,100 @@
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

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

View File

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

View File

@ -1,33 +0,0 @@
// 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

@ -1,22 +0,0 @@
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

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

View File

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

View File

@ -1,7 +0,0 @@
// 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

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

View File

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

View File

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

View File

@ -1,53 +0,0 @@
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

@ -1,52 +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 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
}

54
main.go
View File

@ -14,10 +14,10 @@ import (
"github.com/tcnksm/go-latest"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/converter"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/epub"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/epuboptions"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/utils"
"github.com/celogeek/go-comic-converter/v2/internal/converter"
"github.com/celogeek/go-comic-converter/v2/internal/epub"
epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options"
"github.com/celogeek/go-comic-converter/v2/internal/utils"
)
func main() {
@ -28,22 +28,7 @@ func main() {
cmd.InitParse()
cmd.Parse()
switch {
case cmd.Options.Version:
version()
case cmd.Options.Save:
save(cmd)
case cmd.Options.Show:
show(cmd)
case cmd.Options.Reset:
reset(cmd)
default:
generate(cmd)
}
}
func version() {
if cmd.Options.Version {
bi, ok := debug.ReadBuildInfo()
if !ok {
utils.Println("failed to fetch current version")
@ -77,9 +62,10 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
latestVersion.Segments()[0],
latestVersion.Original(),
)
return
}
func save(cmd *converter.Converter) {
if cmd.Options.Save {
if err := cmd.Options.SaveConfig(); err != nil {
cmd.Fatal(err)
}
@ -89,13 +75,15 @@ func save(cmd *converter.Converter) {
cmd.Options.ShowConfig(),
cmd.Options.FileName(),
)
return
}
func show(cmd *converter.Converter) {
if cmd.Options.Show {
utils.Println(cmd.Options.Header(), cmd.Options.ShowConfig())
return
}
func reset(cmd *converter.Converter) {
if cmd.Options.Reset {
if err := cmd.Options.ResetConfig(); err != nil {
cmd.Fatal(err)
}
@ -105,19 +93,9 @@ func reset(cmd *converter.Converter) {
cmd.Options.ShowConfig(),
cmd.Options.FileName(),
)
if err := cmd.Options.ResetConfig(); err != nil {
cmd.Fatal(err)
}
utils.Printf(
"%s%s\n\nReset default to %s\n",
cmd.Options.Header(),
cmd.Options.ShowConfig(),
cmd.Options.FileName(),
)
return
}
func generate(cmd *converter.Converter) {
if err := cmd.Validate(); err != nil {
cmd.Fatal(err)
}
@ -132,7 +110,7 @@ func generate(cmd *converter.Converter) {
profile := cmd.Options.GetProfile()
if err := epub.New(epuboptions.EPUBOptions{
if err := epub.New(&epuboptions.Options{
Input: cmd.Options.Input,
Output: cmd.Options.Output,
LimitMb: cmd.Options.LimitMb,
@ -146,8 +124,8 @@ func generate(cmd *converter.Converter) {
DryVerbose: cmd.Options.DryVerbose,
Quiet: cmd.Options.Quiet,
Json: cmd.Options.Json,
Image: epuboptions.Image{
Crop: epuboptions.Crop{
Image: &epuboptions.Image{
Crop: &epuboptions.Crop{
Enabled: cmd.Options.Crop,
Left: cmd.Options.CropRatioLeft,
Up: cmd.Options.CropRatioUp,
@ -167,7 +145,7 @@ func generate(cmd *converter.Converter) {
NoBlankImage: cmd.Options.NoBlankImage,
Manga: cmd.Options.Manga,
HasCover: cmd.Options.HasCover,
View: epuboptions.View{
View: &epuboptions.View{
Width: profile.Width,
Height: profile.Height,
AspectRatio: cmd.Options.AspectRatio,