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/schollz/progressbar/v3 v3.14.2
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
)
@ -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.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.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
)

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.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/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/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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.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/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
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/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
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=
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

@ -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"
"time"
"github.com/celogeek/go-comic-converter/v2/internal/converter/options"
"github.com/celogeek/go-comic-converter/v2/internal/utils"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/utils"
)
type Converter struct {
Options *options.Options
Options *Options
Cmd *flag.FlagSet
order []converterOrder
order []order
isZeroValueErrs []error
startAt time.Time
}
// New Create a new parser
func New() *Converter {
o := options.New()
o := NewOptions()
cmd := flag.NewFlagSet("go-comic-converter", flag.ExitOnError)
conv := &Converter{
Options: o,
Cmd: cmd,
order: make([]converterOrder, 0),
order: make([]order, 0),
startAt: time.Now(),
}
@ -48,9 +47,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 converterOrderSection:
case orderSection:
utils.Printf("\n%s:\n", o.Value())
case converterOrderName:
case orderName:
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
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
func (c *Converter) AddStringParam(p *string, name string, value string, usage string) {
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
func (c *Converter) AddIntParam(p *int, name string, value int, usage string) {
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
func (c *Converter) AddFloatParam(p *float64, name string, value float64, usage string) {
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
func (c *Converter) AddBoolParam(p *bool, name string, value bool, usage string) {
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.

View File

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

View File

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

View File

@ -1,5 +1,5 @@
// Package profiles manage supported profiles for go-comic-converter.
package profiles
package converter
import (
"fmt"
@ -13,11 +13,12 @@ type Profile struct {
Height int `json:"height"`
}
type Profiles []Profile
type Profiles map[string]Profile
// New Initialize list of all supported profiles.
func New() Profiles {
return []Profile{
// NewProfiles Initialize list of all supported profiles.
func NewProfiles() Profiles {
res := make(Profiles)
for _, r := range []Profile{
// High Resolution for Tablet
{"HR", "High Resolution", 2400, 3840},
{"SR", "Standard Resolution", 1200, 1920},
@ -50,7 +51,10 @@ func New() Profiles {
// reMarkable
{"RM1", "reMarkable 1", 1404, 1872},
{"RM2", "reMarkable 2", 1404, 1872},
} {
res[r.Code] = r
}
return res
}
func (p Profiles) String() string {
@ -65,13 +69,3 @@ 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,34 +14,33 @@ import (
"github.com/gofrs/uuid"
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"
"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"
)
type EPub struct {
*epuboptions.Options
type EPUB struct {
epuboptions.EPUBOptions
UID string
Publisher string
UpdatedAt string
templateProcessor *template.Template
imageProcessor *epubimageprocessor.EPUBImageProcessor
imageProcessor epubimageprocessor.EPUBImageProcessor
}
type epubPart struct {
Cover *epubimage.Image
Images []*epubimage.Image
Reader *zip.ReadCloser
Cover epubimage.EPUBImage
Images []epubimage.EPUBImage
}
// New initialize EPUB
func New(options *epuboptions.Options) *EPub {
func New(options epuboptions.EPUBOptions) EPUB {
uid := uuid.Must(uuid.NewV4())
tmpl := template.New("parser")
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) },
})
return &EPub{
Options: options,
return EPUB{
EPUBOptions: options,
UID: uid.String(),
Publisher: "GO Comic Converter",
UpdatedAt: time.Now().UTC().Format("2006-01-02T15:04:05Z"),
@ -60,7 +59,7 @@ func New(options *epuboptions.Options) *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 {
@ -70,7 +69,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.Image, zipImg *zip.File) error {
func (e EPUB) writeImage(wz epubzip.EPUBZip, img epubimage.EPUBImage, zipImg *zip.File) error {
err := wz.WriteContent(
img.EPUBPagePath(),
[]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
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(
img.EPUBSpacePath(),
[]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
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"
text := ""
if totalParts > 1 {
@ -119,7 +118,7 @@ func (e *EPub) writeCoverImage(wz *epubzip.EPUBZip, img *epubimage.Image, part,
return err
}
coverTitle, err := e.imageProcessor.CoverTitleData(&epubimageprocessor.CoverTitleDataOptions{
coverTitle, err := e.imageProcessor.CoverTitleData(epubimageprocessor.CoverTitleDataOptions{
Src: img.Raw,
Name: "cover",
Text: text,
@ -142,7 +141,7 @@ func (e *EPub) writeCoverImage(wz *epubzip.EPUBZip, img *epubimage.Image, part,
}
// 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 := ""
if !e.Image.View.PortraitOnly {
if e.Image.Manga {
@ -176,7 +175,7 @@ func (e *EPub) writeTitleImage(wz *epubzip.EPUBZip, img *epubimage.Image, title
return err
}
coverTitle, err := e.imageProcessor.CoverTitleData(&epubimageprocessor.CoverTitleDataOptions{
coverTitle, err := e.imageProcessor.CoverTitleData(epubimageprocessor.CoverTitleDataOptions{
Src: img.Raw,
Name: "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
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 nil, nil, err
return
}
// 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
})
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 parts, nil, nil
return
}
imgStorage, err = epubzip.NewStorageImageReader(e.ImgStorage())
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
@ -239,37 +238,37 @@ func (e *EPub) getParts() (parts []*epubPart, imgStorage *epubzip.StorageImageRe
baseSize := uint64(128*1024) + imgStorage.Size(cover.EPUBImgPath())*2
currentSize := baseSize
currentImages := make([]*epubimage.Image, 0)
currentImages := make([]epubimage.EPUBImage, 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.Image, 0)
currentImages = make([]epubimage.EPUBImage, 0)
}
currentSize += imgSize
currentImages = append(currentImages, img)
}
if len(currentImages) > 0 {
parts = append(parts, &epubPart{
parts = append(parts, epubPart{
Cover: cover,
Images: currentImages,
})
}
return parts, imgStorage, nil
return
}
// create a tree from the directories.
//
// 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()
for _, img := range images {
if skipFiles {
@ -286,7 +285,7 @@ func (e *EPub) getTree(images []*epubimage.Image, 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
@ -313,7 +312,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
}
@ -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)
wz, err := epubzip.New(path)
if err != nil {
return err
}
defer func(wz *epubzip.EPUBZip) {
defer func(wz epubzip.EPUBZip) {
_ = wz.Close()
}(wz)
@ -355,7 +354,7 @@ func (e *EPub) writePart(path string, currentPart, totalParts int, part *epubPar
content := []zipContent{
{"META-INF/container.xml", epubtemplates.Container},
{"META-INF/com.apple.ibooks.display-options.xml", epubtemplates.AppleBooks},
{"OEBPS/content.opf", epubtemplates.Content(&epubtemplates.ContentOptions{
{"OEBPS/content.opf", epubtemplates.Content{
Title: title,
HasTitlePage: hasTitlePage,
UID: e.UID,
@ -367,7 +366,7 @@ func (e *EPub) writePart(path string, currentPart, totalParts int, part *epubPar
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,
@ -413,7 +412,7 @@ func (e *EPub) writePart(path string, currentPart, totalParts int, part *epubPar
}
// create the zip
func (e *EPub) Write() error {
func (e EPUB) Write() error {
epubParts, imgStorage, err := e.getParts()
if err != nil {
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))
if e.DryVerbose {
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))
}

View File

@ -1,4 +1,4 @@
// Package epubimage Image helpers to transform image.
// Package epubimage EPUBImage helpers to transform image.
package epubimage
import (
@ -7,7 +7,7 @@ import (
"strings"
)
type Image struct {
type EPUBImage struct {
Id int
Part int
Raw image.Image
@ -24,47 +24,47 @@ type Image struct {
}
// 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)
}
// SpacePath path of the blank page
func (i *Image) SpacePath() string {
func (i EPUBImage) SpacePath() string {
return fmt.Sprintf("Text/%s.xhtml", i.SpaceKey())
}
// 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())
}
// 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)
}
// 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())
}
// EPUBPagePath page path into the EPUB
func (i *Image) EPUBPagePath() string {
func (i EPUBImage) EPUBPagePath() string {
return fmt.Sprintf("OEBPS/%s", i.PagePath())
}
// 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)
}
// ImgPath image path
func (i *Image) ImgPath() string {
func (i EPUBImage) ImgPath() string {
return fmt.Sprintf("Images/%s.%s", i.ImgKey(), i.Format)
}
// EPUBImgPath image path into the EPUB
func (i *Image) EPUBImgPath() string {
func (i EPUBImage) EPUBImgPath() string {
return fmt.Sprintf("OEBPS/%s", i.ImgPath())
}
@ -72,7 +72,7 @@ func (i *Image) EPUBImgPath() string {
//
// center by default.
// 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)
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, "; ")
}
func (i *Image) RelSize(viewWidth, viewHeight int) (relWidth, relHeight int) {
func (i EPUBImage) 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.Optio
}
// 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

@ -27,8 +27,9 @@ import (
pdfimage "github.com/raff/pdfreader/image"
"github.com/raff/pdfreader/pdfread"
"github.com/celogeek/go-comic-converter/v2/internal/sortpath"
"github.com/celogeek/go-comic-converter/v2/internal/utils"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/sortpath"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/utils"
)
type task struct {
@ -42,7 +43,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":
{
@ -53,7 +54,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
@ -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
f, _ := truetype.Parse(gomonobold.TTF)
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
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)
@ -133,16 +134,16 @@ func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *task, err
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)
@ -169,7 +170,7 @@ func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *task, err
if err != nil {
img = e.corruptedImage(p, fn)
}
output <- &task{
output <- task{
Id: job.Id,
Image: img,
Path: p,
@ -190,7 +191,7 @@ func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *task, err
}
// 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
@ -226,15 +227,15 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err
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)
@ -256,7 +257,7 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err
if err != nil {
img = e.corruptedImage(p, fn)
}
output <- &task{
output <- task{
Id: job.Id,
Image: img,
Path: p,
@ -276,7 +277,7 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err
}
// 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 {
@ -312,7 +313,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
Open func() (io.ReadCloser, error)
}
jobs := make(chan *job)
jobs := make(chan job)
go func() {
defer close(jobs)
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)
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
}}
}
@ -348,14 +349,14 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
} 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)
@ -377,7 +378,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
if err != nil {
img = e.corruptedImage(p, fn)
}
output <- &task{
output <- task{
Id: job.Id,
Image: img,
Path: p,
@ -395,7 +396,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
}
// 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")
@ -404,7 +405,7 @@ func (e *EPUBImageProcessor) loadPdf() (totalImages int, output chan *task, err
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()
@ -419,7 +420,7 @@ func (e *EPUBImageProcessor) loadPdf() (totalImages int, output chan *task, err
if err != nil {
img = e.corruptedImage("", name)
}
output <- &task{
output <- task{
Id: i,
Image: img,
Path: "",

View File

@ -11,25 +11,25 @@ import (
"github.com/disintegration/gift"
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"
"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"
)
type EPUBImageProcessor struct {
*epuboptions.Options
epuboptions.EPUBOptions
}
func New(o *epuboptions.Options) *EPUBImageProcessor {
return &EPUBImageProcessor{o}
func New(o epuboptions.EPUBOptions) EPUBImageProcessor {
return EPUBImageProcessor{o}
}
// Load extract and convert images
func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
images = make([]*epubimage.Image, 0)
func (e EPUBImageProcessor) Load() (images []epubimage.EPUBImage, err error) {
images = make([]epubimage.EPUBImage, 0)
imageCount, imageInput, err := e.load()
if err != nil {
return nil, err
@ -38,7 +38,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
// dry run, skip conversion
if e.Dry {
for img := range imageInput {
images = append(images, &epubimage.Image{
images = append(images, epubimage.EPUBImage{
Id: img.Id,
Path: img.Path,
Name: img.Name,
@ -49,7 +49,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
return images, nil
}
imageOutput := make(chan *epubimage.Image)
imageOutput := make(chan epubimage.EPUBImage)
// processing
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
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 {
_ = bar.Close()
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
}
func (e *EPUBImageProcessor) createImage(src image.Image, r image.Rectangle) draw.Image {
if e.Options.Image.GrayScale {
func (e EPUBImageProcessor) createImage(src image.Image, r image.Rectangle) draw.Image {
if e.EPUBOptions.Image.GrayScale {
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
// 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()
src := input.Image
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()))
g.Draw(dst, src)
return &epubimage.Image{
return epubimage.EPUBImage{
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.Ima
}
// 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
g := gift.New(epubimagefilters.CoverTitle(o.Text, o.Align, o.PctWidth, o.PctMargin, o.MaxFontSize, o.BorderSize))
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
import (
"encoding/json"
"fmt"
"os"
"time"
"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 {
@ -20,18 +21,21 @@ 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 newEpubProgressJson(o)
return &jsonprogress{
o: o,
e: json.NewEncoder(os.Stdout),
}
}
fmtJob := fmt.Sprintf("%%0%dd", len(fmt.Sprint(o.TotalJob)))

View File

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

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"
epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/epubimage"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/epuboptions"
)
type ContentOptions struct {
type Content struct {
Title string
HasTitlePage bool
UID string
Author string
Publisher string
UpdatedAt string
ImageOptions *epuboptions.Image
Cover *epubimage.Image
Images []*epubimage.Image
ImageOptions epuboptions.Image
Cover epubimage.EPUBImage
Images []epubimage.EPUBImage
Current int
Total int
}
@ -31,10 +31,10 @@ type tag struct {
value string
}
// Content create the content file
// Get create the content file
//
//goland:noinspection HttpUrlsUsage,HttpUrlsUsage,HttpUrlsUsage,HttpUrlsUsage
func Content(o *ContentOptions) string {
func (o Content) String() string {
doc := etree.NewDocument()
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("prefix", "rendition: http://www.idpf.org/vocab/rendition/#")
addToElement := func(elm *etree.Element, meth func(o *ContentOptions) []tag) {
for _, p := range meth(o) {
addToElement := func(elm *etree.Element, meth func() []tag) {
for _, p := range meth() {
meta := elm.CreateElement(p.name)
for k, v := range p.attrs {
meta.CreateAttr(k, v)
@ -60,10 +60,10 @@ func Content(o *ContentOptions) 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, getMeta)
addToElement(metadata, o.getMeta)
manifest := pkg.CreateElement("manifest")
addToElement(manifest, getManifest)
addToElement(manifest, o.getManifest)
spine := pkg.CreateElement("spine")
if o.ImageOptions.Manga {
@ -73,13 +73,13 @@ func Content(o *ContentOptions) string {
}
if o.ImageOptions.View.PortraitOnly {
addToElement(spine, getSpinePortrait)
addToElement(spine, o.getSpinePortrait)
} else {
addToElement(spine, getSpineAuto)
addToElement(spine, o.getSpineAuto)
}
guide := pkg.CreateElement("guide")
addToElement(guide, getGuide)
addToElement(guide, o.getGuide)
doc.Indent(2)
r, _ := doc.WriteToString()
@ -88,7 +88,7 @@ func Content(o *ContentOptions) string {
}
// metadata part of the content
func getMeta(o *ContentOptions) []tag {
func (o Content) getMeta() []tag {
metas := []tag{
{"meta", tagAttrs{"property": "dcterms:modified"}, o.UpdatedAt},
{"meta", tagAttrs{"property": "schema:accessMode"}, "visual"},
@ -141,9 +141,9 @@ func getMeta(o *ContentOptions) []tag {
return metas
}
func getManifest(o *ContentOptions) []tag {
func (o Content) getManifest() []tag {
var imageTags, pageTags, spaceTags []tag
addTag := func(img *epubimage.Image, withSpace bool) {
addTag := func(img epubimage.EPUBImage, 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 getManifest(o *ContentOptions) []tag {
}
// spine part of the content
func getSpineAuto(o *ContentOptions) []tag {
func (o Content) getSpineAuto() []tag {
isOnTheRight := !o.ImageOptions.Manga
if o.ImageOptions.AppleBookCompatibility {
isOnTheRight = !isOnTheRight
@ -255,7 +255,7 @@ func getSpineAuto(o *ContentOptions) []tag {
return spine
}
func getSpinePortrait(o *ContentOptions) []tag {
func (o Content) getSpinePortrait() []tag {
var spine []tag
if o.HasTitlePage {
spine = append(spine,
@ -273,7 +273,7 @@ func getSpinePortrait(o *ContentOptions) []tag {
}
// getGuide Section guide of the content
func getGuide(o *ContentOptions) []tag {
func (o Content) getGuide() []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

@ -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"
epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
"github.com/celogeek/go-comic-converter/v2/internal/pkg/epubimage"
)
// Toc create toc
//
//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.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
doc.CreateDirective("DOCTYPE html")

View File

@ -23,7 +23,7 @@ import (
"strings"
)
type Tree struct {
type EPUBTree struct {
nodes map[string]*Node
}
@ -33,19 +33,19 @@ type Node struct {
}
// New initialize tree with a root node
func New() *Tree {
return &Tree{map[string]*Node{
func New() *EPUBTree {
return &EPUBTree{map[string]*Node{
".": {".", []*Node{}},
}}
}
// Root root node
func (n *Tree) Root() *Node {
func (n *EPUBTree) Root() *Node {
return n.nodes["."]
}
// Add the filename to the tree
func (n *Tree) Add(filename string) {
func (n *EPUBTree) 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 nil, err
return EPUBZip{}, 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 *ZipImage) error {
func (e EPUBZip) WriteRaw(raw Image) error {
m, err := e.wz.CreateRaw(raw.Header)
if err != nil {
return err
@ -77,7 +77,7 @@ func (e *EPUBZip) WriteRaw(raw *ZipImage) 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 ZipImage struct {
type Image struct {
Header *zip.FileHeader
Data []byte
}
// 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 (
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 nil, err
return Image{}, err
}
wcdata, err := flate.NewWriter(&cdata, flate.BestCompression)
if err != nil {
return nil, err
return Image{}, err
}
_, err = wcdata.Write(data.Bytes())
if err != nil {
return nil, err
return Image{}, err
}
err = wcdata.Close()
if err != nil {
return nil, err
return Image{}, err
}
t := time.Now()
//goland:noinspection GoDeprecation
return &ZipImage{
return Image{
&zip.FileHeader{
Name: filename,
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/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"
"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"
)
func main() {
@ -28,25 +28,40 @@ func main() {
cmd.InitParse()
cmd.Parse()
if cmd.Options.Version {
bi, ok := debug.ReadBuildInfo()
if !ok {
utils.Println("failed to fetch current version")
os.Exit(1)
}
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)
}
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
Sum : %s
Version : %s
@ -55,47 +70,54 @@ func main() {
To install the latest version:
$ go install github.com/celogeek/go-comic-converter/v%d@%s
`,
bi.Main.Path,
bi.Main.Sum,
bi.Main.Version,
latestVersion.Original(),
latestVersion.Segments()[0],
latestVersion.Original(),
)
return
}
bi.Main.Path,
bi.Main.Sum,
bi.Main.Version,
latestVersion.Original(),
latestVersion.Segments()[0],
latestVersion.Original(),
)
}
if cmd.Options.Save {
if err := cmd.Options.SaveConfig(); err != nil {
cmd.Fatal(err)
}
utils.Printf(
"%s%s\n\nSaving to %s\n",
cmd.Options.Header(),
cmd.Options.ShowConfig(),
cmd.Options.FileName(),
)
return
func save(cmd *converter.Converter) {
if err := cmd.Options.SaveConfig(); err != nil {
cmd.Fatal(err)
}
utils.Printf(
"%s%s\n\nSaving to %s\n",
cmd.Options.Header(),
cmd.Options.ShowConfig(),
cmd.Options.FileName(),
)
}
if cmd.Options.Show {
utils.Println(cmd.Options.Header(), cmd.Options.ShowConfig())
return
func show(cmd *converter.Converter) {
utils.Println(cmd.Options.Header(), cmd.Options.ShowConfig())
}
func reset(cmd *converter.Converter) {
if err := cmd.Options.ResetConfig(); err != nil {
cmd.Fatal(err)
}
if cmd.Options.Reset {
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
utils.Printf(
"%s%s\n\nReset default to %s\n",
cmd.Options.Header(),
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(),
)
}
func generate(cmd *converter.Converter) {
if err := cmd.Validate(); err != nil {
cmd.Fatal(err)
}
@ -110,7 +132,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
profile := cmd.Options.GetProfile()
if err := epub.New(&epuboptions.Options{
if err := epub.New(epuboptions.EPUBOptions{
Input: cmd.Options.Input,
Output: cmd.Options.Output,
LimitMb: cmd.Options.LimitMb,
@ -124,8 +146,8 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
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,
@ -145,7 +167,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
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,