Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New covergen tool #44

Merged
merged 18 commits into from
Nov 25, 2019
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 123 additions & 6 deletions covergen/covergen.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package main

import (
"archive/zip"
"errors"
"fmt"
"image"
_ "image/gif"
"image/jpeg"
_ "image/png"
"os"
"path"
"path/filepath"
"reflect"
"strings"

"github.com/bamiaux/rez"
"github.com/beevik/etree"
"github.com/geek1011/koboutils/kobo"
"github.com/mattn/go-zglob"
"github.com/spf13/pflag"
Expand All @@ -19,6 +26,7 @@ var version = "dev"
func main() {
regenerate := pflag.BoolP("regenerate", "r", false, "Re-generate all covers")
method := pflag.StringP("method", "m", "lanczos3", "Resize algorithm to use (bilinear, bicubic, lanczos2, lanczos3)")
// TODO: invert, grayscale options
help := pflag.BoolP("help", "h", false, "Show this help message")
pflag.Parse()

Expand Down Expand Up @@ -117,6 +125,8 @@ func main() {
continue
}

//fmt.Println(ct, origCover.Bounds().Size(), resized.Bounds().Size())

if err := save(cp, resized, jpeg.DefaultQuality); err != nil {
fmt.Fprintf(os.Stderr, "--------- Could not save cover: %v.\n", err)
ne++
Expand Down Expand Up @@ -147,8 +157,8 @@ func imageID(kp, book string) (string, error) {
}

func check(ct kobo.CoverType, kp, iid string) (string, bool, error) {
cp := ct.GeneratePath(false, iid)
if _, err := os.Stat(filepath.Join(kp, cp)); err == nil {
cp := filepath.Join(kp, filepath.FromSlash(ct.GeneratePath(false, iid)))
if _, err := os.Stat(cp); err == nil {
return cp, true, nil
} else if os.IsNotExist(err) {
return cp, false, nil
Expand All @@ -158,15 +168,122 @@ func check(ct kobo.CoverType, kp, iid string) (string, bool, error) {
}

func extract(epub string) (image.Image, error) {
return nil, errors.New("not implemented")
zr, err := zip.OpenReader(epub)
if err != nil {
return nil, err
}
defer zr.Close()

var rootfile string
for _, f := range zr.File {
if strings.TrimLeft(strings.ToLower(f.Name), "/") == "meta-inf/container.xml" {
doc := etree.NewDocument()
if rc, err := f.Open(); err != nil {
return nil, fmt.Errorf("could not open container.xml: %w", err)
} else if _, err = doc.ReadFrom(rc); err != nil {
rc.Close()
return nil, fmt.Errorf("could not parse container.xml: %w", err)
} else {
rc.Close()
}

if el := doc.FindElement("//rootfiles/rootfile[@full-path]"); el != nil {
rootfile = el.SelectAttrValue("full-path", "")
}
break
}
}
if rootfile == "" {
return nil, errors.New("could not open ebook: could not find package document")
}

var covers []string
for _, f := range zr.File {
if strings.TrimLeft(strings.ToLower(f.Name), "/") == strings.TrimLeft(strings.ToLower(rootfile), "/") {
doc := etree.NewDocument()
if rc, err := f.Open(); err != nil {
return nil, fmt.Errorf("could not open container.xml: %w", err)
pgaskin marked this conversation as resolved.
Show resolved Hide resolved
} else if _, err = doc.ReadFrom(rc); err != nil {
rc.Close()
return nil, fmt.Errorf("could not parse container.xml: %w", err)
pgaskin marked this conversation as resolved.
Show resolved Hide resolved
} else {
rc.Close()
}

if el := doc.FindElement("//meta[@name='cover']"); el != nil {
content := el.SelectAttrValue("content", "")
if content != "" {
covers = append(covers, content) // some put the path directly in the value
if iel := doc.FindElement("//manifest/item[@id='" + content + "']"); iel != nil {
if href := iel.SelectAttrValue("href", ""); href != "" {
covers = append(covers, href) // most have it as an id for a manifest item
}
}
}
}

if el := doc.FindElement("//manifest/item[@properties='cover-image']"); el != nil {
if href := el.SelectAttrValue("href", ""); href != "" {
covers = append(covers, href) // pure epub3 books have it as a manifest item
}
}

// most books have it relative to the opf
for _, cover := range covers {
covers = append(covers, path.Join(path.Dir(f.Name), cover))
}

break
}
}

var img image.Image
for _, cover := range covers {
for _, f := range zr.File {
if strings.TrimLeft(strings.ToLower(f.Name), "/") == strings.TrimLeft(strings.ToLower(cover), "/") {
if rc, err := f.Open(); err != nil {
return nil, fmt.Errorf("could not open image %s: %w", cover, err)
} else if img, _, err = image.Decode(rc); err != nil {
rc.Close()
return nil, fmt.Errorf("could not parse image %s: %w", cover, err)
} else {
rc.Close()
break
}
}
}
}

return img, nil // img may be nil
}

func resize(dev kobo.Device, ct kobo.CoverType, filter rez.Filter, orig image.Image) (image.Image, error) {
return nil, errors.New("not implemented")
szo := orig.Bounds().Size()
szn := dev.CoverSized(ct, szo)

if szn.Eq(szo) {
return orig, nil
}

origy, ok := orig.(*image.YCbCr)
if !ok {
return nil, fmt.Errorf("unsupported image encoding (not YCbCr): %s", reflect.TypeOf(orig))
}

new := image.NewYCbCr(image.Rect(0, 0, szn.X, szn.Y), origy.SubsampleRatio)
if err := rez.Convert(new, orig, filter); err != nil {
return nil, err
}

return new, nil
}

func save(path string, img image.Image, quality int) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
func save(fn string, img image.Image, quality int) error {
if err := os.MkdirAll(path.Dir(filepath.ToSlash(fn)), 0755); err != nil {
return err
}

f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
Expand Down