mirror of
https://github.com/celogeek/go-comic-converter.git
synced 2025-05-24 15:52:38 +02:00
Compare commits
14 Commits
2ce29d62ec
...
93294ba1f9
Author | SHA1 | Date | |
---|---|---|---|
93294ba1f9 | |||
cdd3668ca2 | |||
30ac67b06d | |||
577d51a819 | |||
a76f29e91d | |||
3c582ae52d | |||
a351106eb7 | |||
3d31108aba | |||
a94e67dd06 | |||
5404390fd8 | |||
b1e246240c | |||
e10eeadc4e | |||
5c7775cd47 | |||
113e7ebcb0 |
8
go.mod
8
go.mod
@ -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
16
go.sum
@ -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=
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
)
|
@ -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)
|
||||
}
|
@ -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.
|
@ -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
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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
|
@ -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)
|
||||
}
|
@ -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
|
@ -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: "",
|
@ -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
|
5
internal/pkg/epuboptions/color.go
Normal file
5
internal/pkg/epuboptions/color.go
Normal file
@ -0,0 +1,5 @@
|
||||
package epuboptions
|
||||
|
||||
type Color struct {
|
||||
Foreground, Background string
|
||||
}
|
8
internal/pkg/epuboptions/crop.go
Normal file
8
internal/pkg/epuboptions/crop.go
Normal file
@ -0,0 +1,8 @@
|
||||
package epuboptions
|
||||
|
||||
type Crop struct {
|
||||
Enabled bool
|
||||
Left, Up, Right, Bottom int
|
||||
Limit int
|
||||
SkipIfLimitReached bool
|
||||
}
|
33
internal/pkg/epuboptions/epub_options.go
Normal file
33
internal/pkg/epuboptions/epub_options.go
Normal 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)
|
||||
}
|
22
internal/pkg/epuboptions/image.go
Normal file
22
internal/pkg/epuboptions/image.go
Normal 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
|
||||
}
|
8
internal/pkg/epuboptions/view.go
Normal file
8
internal/pkg/epuboptions/view.go
Normal file
@ -0,0 +1,8 @@
|
||||
package epuboptions
|
||||
|
||||
type View struct {
|
||||
Width, Height int
|
||||
AspectRatio float64
|
||||
PortraitOnly bool
|
||||
Color Color
|
||||
}
|
@ -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)))
|
@ -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
|
||||
}
|
6
internal/pkg/epubtemplates/applebooks.go
Normal file
6
internal/pkg/epubtemplates/applebooks.go
Normal file
@ -0,0 +1,6 @@
|
||||
package epubtemplates
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed "applebooks.xml.tmpl"
|
||||
var AppleBooks string
|
7
internal/pkg/epubtemplates/blank.go
Normal file
7
internal/pkg/epubtemplates/blank.go
Normal 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
|
6
internal/pkg/epubtemplates/container.go
Normal file
6
internal/pkg/epubtemplates/container.go
Normal file
@ -0,0 +1,6 @@
|
||||
package epubtemplates
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed "container.xml.tmpl"
|
||||
var Container string
|
@ -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()}, ""},
|
6
internal/pkg/epubtemplates/style.go
Normal file
6
internal/pkg/epubtemplates/style.go
Normal file
@ -0,0 +1,6 @@
|
||||
package epubtemplates
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed "style.css.tmpl"
|
||||
var Style string
|
6
internal/pkg/epubtemplates/text.go
Normal file
6
internal/pkg/epubtemplates/text.go
Normal file
@ -0,0 +1,6 @@
|
||||
package epubtemplates
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed "text.xhtml.tmpl"
|
||||
var Text string
|
@ -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")
|
@ -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)) {
|
@ -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(),
|
@ -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()),
|
53
internal/pkg/epubzip/storage_image_reader.go
Normal file
53
internal/pkg/epubzip/storage_image_reader.go
Normal 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)
|
||||
}
|
52
internal/pkg/epubzip/storage_image_writer.go
Normal file
52
internal/pkg/epubzip/storage_image_writer.go
Normal 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
142
main.go
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user