simplify, make only necessary public method

This commit is contained in:
Celogeek 2022-12-29 21:18:02 +01:00
parent 222e02f377
commit 7a27473cb3
Signed by: celogeek
GPG Key ID: E6B7BDCFC446233A
5 changed files with 116 additions and 159 deletions

View File

@ -19,40 +19,42 @@ import (
imageconverter "go-comic-converter/internal/image-converter" imageconverter "go-comic-converter/internal/image-converter"
) )
type Image struct { type image struct {
Id int Id int
Data *ImageData Data *imageData
Width int Width int
Height int Height int
} }
type EPub struct { type EpubOptions struct {
Path string Input string
Output string
UID string
Title string Title string
Author string Author string
Publisher string
UpdatedAt string
ViewWidth int ViewWidth int
ViewHeight int ViewHeight int
Quality int Quality int
Crop bool Crop bool
LimitMb int LimitMb int
Error error
ImagesCount int
ProcessingImages func() chan *Image
TemplateProcessor *template.Template
} }
type EpubPart struct { type ePub struct {
Cover *Image *EpubOptions
Images []*Image UID string
Publisher string
UpdatedAt string
imagesCount int
processingImages func() chan *image
templateProcessor *template.Template
} }
func NewEpub(path string) *EPub { type epubPart struct {
Cover *image
Images []*image
}
func NewEpub(options *EpubOptions) *ePub {
uid, err := uuid.NewV4() uid, err := uuid.NewV4()
if err != nil { if err != nil {
panic(err) panic(err)
@ -64,55 +66,17 @@ func NewEpub(path string) *EPub {
"zoom": func(s int, z float32) int { return int(float32(s) * z) }, "zoom": func(s int, z float32) int { return int(float32(s) * z) },
}) })
return &EPub{ return &ePub{
Path: path, EpubOptions: options,
UID: uid.String(),
UID: uid.String(), Publisher: "GO Comic Converter",
Title: "Unknown title", UpdatedAt: time.Now().UTC().Format("2006-01-02T15:04:05Z"),
Author: "Unknown author", templateProcessor: tmpl,
Publisher: "GO Comic Converter",
UpdatedAt: time.Now().UTC().Format("2006-01-02T15:04:05Z"),
ViewWidth: 0,
ViewHeight: 0,
Quality: 75,
TemplateProcessor: tmpl,
} }
} }
func (e *EPub) SetTitle(title string) *EPub { func (e *ePub) render(templateString string, data any) string {
e.Title = title tmpl, err := e.templateProcessor.Parse(templateString)
return e
}
func (e *EPub) SetAuthor(author string) *EPub {
e.Author = author
return e
}
func (e *EPub) SetSize(w, h int) *EPub {
e.ViewWidth = w
e.ViewHeight = h
return e
}
func (e *EPub) SetQuality(q int) *EPub {
e.Quality = q
return e
}
func (e *EPub) SetCrop(c bool) *EPub {
e.Crop = c
return e
}
func (e *EPub) SetLimitMb(l int) *EPub {
e.LimitMb = l
return e
}
func (e *EPub) Render(templateString string, data any) string {
tmpl, err := e.TemplateProcessor.Parse(templateString)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -124,34 +88,32 @@ func (e *EPub) Render(templateString string, data any) string {
return result.String() return result.String()
} }
func (e *EPub) Load(path string) *EPub { func (e *ePub) load() error {
fi, err := os.Stat(path) fi, err := os.Stat(e.Input)
if err != nil { if err != nil {
e.Error = err return err
return e
} }
if fi.IsDir() { if fi.IsDir() {
return e.LoadDir(path) return e.loadDir()
} }
switch ext := strings.ToLower(filepath.Ext(path)); ext { switch ext := strings.ToLower(filepath.Ext(e.Input)); ext {
case ".cbz": case ".cbz":
e.LoadCBZ(path) return e.loadCBZ()
case ".cbr": case ".cbr":
e.LoadCBR(path) return e.loadCBR()
case ".pdf": case ".pdf":
e.LoadPDF(path) return e.loadPDF()
default: default:
e.Error = fmt.Errorf("unknown file format (%s): support .cbz, .cbr, .pdf", ext) return fmt.Errorf("unknown file format (%s): support .cbz, .cbr, .pdf", ext)
} }
return e
} }
func (e *EPub) LoadCBZ(path string) *EPub { func (e *ePub) loadCBZ() error {
r, err := zip.OpenReader(path) r, err := zip.OpenReader(e.Input)
if err != nil { if err != nil {
e.Error = err return err
} }
images := make([]*zip.File, 0) images := make([]*zip.File, 0)
@ -165,16 +127,15 @@ func (e *EPub) LoadCBZ(path string) *EPub {
images = append(images, f) images = append(images, f)
} }
if len(images) == 0 { if len(images) == 0 {
e.Error = fmt.Errorf("no images found")
r.Close() r.Close()
return e return fmt.Errorf("no images found")
} }
sort.SliceStable(images, func(i, j int) bool { sort.SliceStable(images, func(i, j int) bool {
return strings.Compare(images[i].Name, images[j].Name) < 0 return strings.Compare(images[i].Name, images[j].Name) < 0
}) })
e.ImagesCount = len(images) e.imagesCount = len(images)
type Todo struct { type Todo struct {
Id int Id int
@ -183,10 +144,10 @@ func (e *EPub) LoadCBZ(path string) *EPub {
todo := make(chan *Todo) todo := make(chan *Todo)
e.ProcessingImages = func() chan *Image { e.processingImages = func() chan *image {
// defer r.Close() // defer r.Close()
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
results := make(chan *Image) results := make(chan *image)
for i := 0; i < runtime.NumCPU(); i++ { for i := 0; i < runtime.NumCPU(); i++ {
wg.Add(1) wg.Add(1)
go func() { go func() {
@ -207,9 +168,9 @@ func (e *EPub) LoadCBZ(path string) *EPub {
if task.Id == 0 { if task.Id == 0 {
name = "OEBPS/Images/cover.jpg" name = "OEBPS/Images/cover.jpg"
} }
results <- &Image{ results <- &image{
task.Id, task.Id,
NewImageData(name, data), newImageData(name, data),
w, w,
h, h,
} }
@ -229,22 +190,20 @@ func (e *EPub) LoadCBZ(path string) *EPub {
return results return results
} }
return e return nil
} }
func (e *EPub) LoadCBR(path string) *EPub { func (e *ePub) loadCBR() error {
e.Error = fmt.Errorf("no implemented") return fmt.Errorf("no implemented")
return e
} }
func (e *EPub) LoadPDF(path string) *EPub { func (e *ePub) loadPDF() error {
e.Error = fmt.Errorf("no implemented") return fmt.Errorf("no implemented")
return e
} }
func (e *EPub) LoadDir(dirname string) *EPub { func (e *ePub) loadDir() error {
images := make([]string, 0) images := make([]string, 0)
err := filepath.WalkDir(dirname, func(path string, d fs.DirEntry, err error) error { err := filepath.WalkDir(e.Input, func(path string, d fs.DirEntry, err error) error {
if err != nil { if err != nil {
return err return err
} }
@ -260,16 +219,14 @@ func (e *EPub) LoadDir(dirname string) *EPub {
return nil return nil
}) })
if err != nil { if err != nil {
e.Error = err return err
return e
} }
if len(images) == 0 { if len(images) == 0 {
e.Error = fmt.Errorf("no images found") return fmt.Errorf("no images found")
return e
} }
sort.Strings(images) sort.Strings(images)
e.ImagesCount = len(images) e.imagesCount = len(images)
type Todo struct { type Todo struct {
Id int Id int
@ -278,9 +235,9 @@ func (e *EPub) LoadDir(dirname string) *EPub {
todo := make(chan *Todo) todo := make(chan *Todo)
e.ProcessingImages = func() chan *Image { e.processingImages = func() chan *image {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
results := make(chan *Image) results := make(chan *image)
for i := 0; i < runtime.NumCPU(); i++ { for i := 0; i < runtime.NumCPU(); i++ {
wg.Add(1) wg.Add(1)
go func() { go func() {
@ -301,9 +258,9 @@ func (e *EPub) LoadDir(dirname string) *EPub {
if task.Id == 0 { if task.Id == 0 {
name = "OEBPS/Images/cover.jpg" name = "OEBPS/Images/cover.jpg"
} }
results <- &Image{ results <- &image{
task.Id, task.Id,
NewImageData(name, data), newImageData(name, data),
w, w,
h, h,
} }
@ -321,27 +278,27 @@ func (e *EPub) LoadDir(dirname string) *EPub {
return results return results
} }
return e return nil
} }
func (e *EPub) GetParts() []*EpubPart { func (e *ePub) getParts() []*epubPart {
images := make([]*Image, e.ImagesCount) images := make([]*image, e.imagesCount)
bar := progressbar.Default(int64(e.ImagesCount), "Processing") bar := progressbar.Default(int64(e.imagesCount), "Processing")
for img := range e.ProcessingImages() { for img := range e.processingImages() {
images[img.Id] = img images[img.Id] = img
bar.Add(1) bar.Add(1)
} }
bar.Close() bar.Close()
epubPart := make([]*EpubPart, 0) parts := make([]*epubPart, 0)
cover := images[0] cover := images[0]
images = images[1:] images = images[1:]
if e.LimitMb == 0 { if e.LimitMb == 0 {
epubPart = append(epubPart, &EpubPart{ parts = append(parts, &epubPart{
Cover: cover, Cover: cover,
Images: images, Images: images,
}) })
return epubPart return parts
} }
maxSize := uint64(e.LimitMb * 1024 * 1024) maxSize := uint64(e.LimitMb * 1024 * 1024)
@ -351,36 +308,36 @@ func (e *EPub) GetParts() []*EpubPart {
baseSize := uint64(16*1024) + cover.Data.CompressedSize() baseSize := uint64(16*1024) + cover.Data.CompressedSize()
currentSize := baseSize currentSize := baseSize
currentImages := make([]*Image, 0) currentImages := make([]*image, 0)
part := 1 part := 1
for _, img := range images { for _, img := range images {
imgSize := img.Data.CompressedSize() + xhtmlSize imgSize := img.Data.CompressedSize() + xhtmlSize
if len(currentImages) > 0 && currentSize+imgSize > maxSize { if len(currentImages) > 0 && currentSize+imgSize > maxSize {
epubPart = append(epubPart, &EpubPart{ parts = append(parts, &epubPart{
Cover: cover, Cover: cover,
Images: currentImages, Images: currentImages,
}) })
part += 1 part += 1
currentSize = baseSize currentSize = baseSize
currentImages = make([]*Image, 0) currentImages = make([]*image, 0)
} }
currentSize += imgSize currentSize += imgSize
currentImages = append(currentImages, img) currentImages = append(currentImages, img)
} }
if len(currentImages) > 0 { if len(currentImages) > 0 {
epubPart = append(epubPart, &EpubPart{ parts = append(parts, &epubPart{
Cover: cover, Cover: cover,
Images: currentImages, Images: currentImages,
}) })
} }
return epubPart return parts
} }
func (e *EPub) Write() error { func (e *ePub) Write() error {
if e.Error != nil { if err := e.load(); err != nil {
return e.Error return err
} }
type ZipContent struct { type ZipContent struct {
@ -388,31 +345,31 @@ func (e *EPub) Write() error {
Content any Content any
} }
epubParts := e.GetParts() epubParts := e.getParts()
totalParts := len(epubParts) totalParts := len(epubParts)
bar := progressbar.Default(int64(totalParts), "Writing Part") bar := progressbar.Default(int64(totalParts), "Writing Part")
for i, part := range epubParts { for i, part := range epubParts {
ext := filepath.Ext(e.Path) ext := filepath.Ext(e.Output)
suffix := "" suffix := ""
if totalParts > 1 { if totalParts > 1 {
suffix = fmt.Sprintf(" PART_%02d", i+1) suffix = fmt.Sprintf(" PART_%02d", i+1)
} }
path := fmt.Sprintf("%s%s%s", e.Path[0:len(e.Path)-len(ext)], suffix, ext) path := fmt.Sprintf("%s%s%s", e.Output[0:len(e.Output)-len(ext)], suffix, ext)
wz, err := NewEpubZip(path) wz, err := newEpubZip(path)
if err != nil { if err != nil {
return err return err
} }
defer wz.Close() defer wz.Close()
zipContent := []ZipContent{ zipContent := []ZipContent{
{"META-INF/container.xml", TEMPLATE_CONTAINER}, {"META-INF/container.xml", containerTmpl},
{"OEBPS/content.opf", e.Render(TEMPLATE_CONTENT, map[string]any{"Info": e, "Images": part.Images})}, {"OEBPS/content.opf", e.render(contentTmpl, map[string]any{"Info": e, "Images": part.Images})},
{"OEBPS/toc.ncx", e.Render(TEMPLATE_TOC, map[string]any{"Info": e, "Image": part.Images[0]})}, {"OEBPS/toc.ncx", e.render(tocTmpl, map[string]any{"Info": e, "Image": part.Images[0]})},
{"OEBPS/nav.xhtml", e.Render(TEMPLATE_NAV, map[string]any{"Info": e, "Image": part.Images[0]})}, {"OEBPS/nav.xhtml", e.render(navTmpl, map[string]any{"Info": e, "Image": part.Images[0]})},
{"OEBPS/Text/style.css", TEMPLATE_STYLE}, {"OEBPS/Text/style.css", styleTmpl},
{"OEBPS/Text/part.xhtml", e.Render(TEMPLATE_PART, map[string]any{ {"OEBPS/Text/part.xhtml", e.render(partTmpl, map[string]any{
"Info": e, "Info": e,
"Part": i + 1, "Part": i + 1,
"Total": totalParts, "Total": totalParts,
@ -431,7 +388,7 @@ func (e *EPub) Write() error {
for _, img := range part.Images { for _, img := range part.Images {
text := fmt.Sprintf("OEBPS/Text/%d.xhtml", img.Id) text := fmt.Sprintf("OEBPS/Text/%d.xhtml", img.Id)
if err := wz.WriteFile(text, e.Render(TEMPLATE_TEXT, img)); err != nil { if err := wz.WriteFile(text, e.render(textTmpl, img)); err != nil {
return err return err
} }
if err := wz.WriteImage(img.Data); err != nil { if err := wz.WriteImage(img.Data); err != nil {

View File

@ -8,16 +8,16 @@ import (
"time" "time"
) )
type ImageData struct { type imageData struct {
Header *zip.FileHeader Header *zip.FileHeader
Data []byte Data []byte
} }
func (img *ImageData) CompressedSize() uint64 { func (img *imageData) CompressedSize() uint64 {
return img.Header.CompressedSize64 + 30 + uint64(len(img.Header.Name)) return img.Header.CompressedSize64 + 30 + uint64(len(img.Header.Name))
} }
func NewImageData(name string, data []byte) *ImageData { func newImageData(name string, data []byte) *imageData {
cdata := bytes.NewBuffer([]byte{}) cdata := bytes.NewBuffer([]byte{})
wcdata, err := flate.NewWriter(cdata, flate.BestCompression) wcdata, err := flate.NewWriter(cdata, flate.BestCompression)
if err != nil { if err != nil {
@ -29,7 +29,7 @@ func NewImageData(name string, data []byte) *ImageData {
panic(err) panic(err)
} }
t := time.Now() t := time.Now()
return &ImageData{ return &imageData{
&zip.FileHeader{ &zip.FileHeader{
Name: name, Name: name,
CompressedSize64: uint64(cdata.Len()), CompressedSize64: uint64(cdata.Len()),

View File

@ -3,22 +3,22 @@ package epub
import _ "embed" import _ "embed"
//go:embed "templates/container.xml.tmpl" //go:embed "templates/container.xml.tmpl"
var TEMPLATE_CONTAINER string var containerTmpl string
//go:embed "templates/content.opf.tmpl" //go:embed "templates/content.opf.tmpl"
var TEMPLATE_CONTENT string var contentTmpl string
//go:embed "templates/toc.ncx.tmpl" //go:embed "templates/toc.ncx.tmpl"
var TEMPLATE_TOC string var tocTmpl string
//go:embed "templates/nav.xhtml.tmpl" //go:embed "templates/nav.xhtml.tmpl"
var TEMPLATE_NAV string var navTmpl string
//go:embed "templates/style.css.tmpl" //go:embed "templates/style.css.tmpl"
var TEMPLATE_STYLE string var styleTmpl string
//go:embed "templates/part.xhtml.tmpl" //go:embed "templates/part.xhtml.tmpl"
var TEMPLATE_PART string var partTmpl string
//go:embed "templates/text.xhtml.tmpl" //go:embed "templates/text.xhtml.tmpl"
var TEMPLATE_TEXT string var textTmpl string

View File

@ -7,28 +7,28 @@ import (
"time" "time"
) )
type EpubZip struct { type epubZip struct {
w *os.File w *os.File
wz *zip.Writer wz *zip.Writer
} }
func NewEpubZip(path string) (*EpubZip, error) { func newEpubZip(path string) (*epubZip, error) {
w, err := os.Create(path) w, err := os.Create(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
wz := zip.NewWriter(w) wz := zip.NewWriter(w)
return &EpubZip{w, wz}, nil return &epubZip{w, wz}, nil
} }
func (e *EpubZip) Close() error { func (e *epubZip) Close() error {
if err := e.wz.Close(); err != nil { if err := e.wz.Close(); err != nil {
return err return err
} }
return e.w.Close() return e.w.Close()
} }
func (e *EpubZip) WriteMagic() error { func (e *epubZip) WriteMagic() error {
t := time.Now() t := time.Now()
fh := &zip.FileHeader{ fh := &zip.FileHeader{
Name: "mimetype", Name: "mimetype",
@ -50,7 +50,7 @@ func (e *EpubZip) WriteMagic() error {
return err return err
} }
func (e *EpubZip) WriteImage(image *ImageData) error { func (e *epubZip) WriteImage(image *imageData) error {
m, err := e.wz.CreateRaw(image.Header) m, err := e.wz.CreateRaw(image.Header)
if err != nil { if err != nil {
return err return err
@ -59,7 +59,7 @@ func (e *EpubZip) WriteImage(image *ImageData) error {
return err return err
} }
func (e *EpubZip) WriteFile(file string, data any) error { func (e *epubZip) WriteFile(file string, data any) error {
var content []byte var content []byte
switch b := data.(type) { switch b := data.(type) {
case string: case string:

22
main.go
View File

@ -124,17 +124,17 @@ func main() {
fmt.Println(opt) fmt.Println(opt)
err := epub.NewEpub(opt.Output). if err := epub.NewEpub(&epub.EpubOptions{
SetSize(profile.Width, profile.Height). Input: opt.Input,
SetQuality(opt.Quality). Output: opt.Output,
SetCrop(!opt.NoCrop). ViewWidth: profile.Width,
SetLimitMb(opt.LimitMb). ViewHeight: profile.Height,
SetTitle(opt.Title). Quality: opt.Quality,
SetAuthor(opt.Author). Crop: !opt.NoCrop,
Load(opt.Input). LimitMb: opt.LimitMb,
Write() Title: opt.Title,
Author: opt.Author,
if err != nil { }).Write(); err != nil {
fmt.Printf("Error: %v\n", err) fmt.Printf("Error: %v\n", err)
os.Exit(1) os.Exit(1)
} }