Compare commits

..

No commits in common. "8b04cbc38f219533779843f2703d8a85ff9947bb" and "e6051fda887e30ad4d41248a0a4623f57a776493" have entirely different histories.

10 changed files with 85 additions and 143 deletions

View File

@ -14,7 +14,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp"
"runtime" "runtime"
"strings" "strings"
"time" "time"
@ -101,7 +100,6 @@ func (c *Converter) InitParse() {
c.AddSection("Config") c.AddSection("Config")
c.AddStringParam(&c.Options.Profile, "profile", c.Options.Profile, fmt.Sprintf("Profile to use: \n%s", c.Options.AvailableProfiles())) c.AddStringParam(&c.Options.Profile, "profile", c.Options.Profile, fmt.Sprintf("Profile to use: \n%s", c.Options.AvailableProfiles()))
c.AddIntParam(&c.Options.Quality, "quality", c.Options.Quality, "Quality of the image") c.AddIntParam(&c.Options.Quality, "quality", c.Options.Quality, "Quality of the image")
c.AddBoolParam(&c.Options.Grayscale, "grayscale", c.Options.Grayscale, "Grayscale image. Ideal for eInk devices.")
c.AddBoolParam(&c.Options.Crop, "crop", c.Options.Crop, "Crop images") c.AddBoolParam(&c.Options.Crop, "crop", c.Options.Crop, "Crop images")
c.AddIntParam(&c.Options.CropRatioLeft, "crop-ratio-left", c.Options.CropRatioLeft, "Crop ratio left: ratio of pixels allow to be non blank while cutting on the left.") c.AddIntParam(&c.Options.CropRatioLeft, "crop-ratio-left", c.Options.CropRatioLeft, "Crop ratio left: ratio of pixels allow to be non blank while cutting on the left.")
c.AddIntParam(&c.Options.CropRatioUp, "crop-ratio-up", c.Options.CropRatioUp, "Crop ratio up: ratio of pixels allow to be non blank while cutting on the top.") c.AddIntParam(&c.Options.CropRatioUp, "crop-ratio-up", c.Options.CropRatioUp, "Crop ratio up: ratio of pixels allow to be non blank while cutting on the top.")
@ -118,8 +116,6 @@ func (c *Converter) InitParse() {
c.AddIntParam(&c.Options.LimitMb, "limitmb", c.Options.LimitMb, "Limit size of the EPUB: Default nolimit (0), Minimum 20") c.AddIntParam(&c.Options.LimitMb, "limitmb", c.Options.LimitMb, "Limit size of the EPUB: Default nolimit (0), Minimum 20")
c.AddBoolParam(&c.Options.StripFirstDirectoryFromToc, "strip", c.Options.StripFirstDirectoryFromToc, "Strip first directory from the TOC if only 1") c.AddBoolParam(&c.Options.StripFirstDirectoryFromToc, "strip", c.Options.StripFirstDirectoryFromToc, "Strip first directory from the TOC if only 1")
c.AddIntParam(&c.Options.SortPathMode, "sort", c.Options.SortPathMode, "Sort path mode\n0 = alpha for path and file\n1 = alphanum for path and alpha for file\n2 = alphanum for path and file") c.AddIntParam(&c.Options.SortPathMode, "sort", c.Options.SortPathMode, "Sort path mode\n0 = alpha for path and file\n1 = alphanum for path and alpha for file\n2 = alphanum for path and file")
c.AddStringParam(&c.Options.ForegroundColor, "foreground-color", c.Options.ForegroundColor, "Foreground color in hexa format RGB. Black=000, White=FFF")
c.AddStringParam(&c.Options.BackgroundColor, "background-color", c.Options.BackgroundColor, "Background color in hexa format RGB. Black=000, White=FFF, Light Gray=DDD, Dark Gray=777")
c.AddSection("Default config") c.AddSection("Default config")
c.AddBoolParam(&c.Options.Show, "show", false, "Show your default parameters") c.AddBoolParam(&c.Options.Show, "show", false, "Show your default parameters")
@ -297,16 +293,6 @@ func (c *Converter) Validate() error {
return errors.New("sort should be 0, 1 or 2") return errors.New("sort should be 0, 1 or 2")
} }
// Color
colorRegex := regexp.MustCompile("^[0-9A-F]{3}$")
if !colorRegex.MatchString(c.Options.ForegroundColor) {
return errors.New("foreground color must have color format in hexa: [0-9A-F]{3}")
}
if !colorRegex.MatchString(c.Options.BackgroundColor) {
return errors.New("background color must have color format in hexa: [0-9A-F]{3}")
}
return nil return nil
} }

View File

@ -23,7 +23,6 @@ type Options struct {
// Config // Config
Profile string `yaml:"profile"` Profile string `yaml:"profile"`
Quality int `yaml:"quality"` Quality int `yaml:"quality"`
Grayscale bool `yaml:"grayscale"`
Crop bool `yaml:"crop"` Crop bool `yaml:"crop"`
CropRatioLeft int `yaml:"crop_ratio_left"` CropRatioLeft int `yaml:"crop_ratio_left"`
CropRatioUp int `yaml:"crop_ratio_up"` CropRatioUp int `yaml:"crop_ratio_up"`
@ -40,8 +39,6 @@ type Options struct {
LimitMb int `yaml:"limit_mb"` LimitMb int `yaml:"limit_mb"`
StripFirstDirectoryFromToc bool `yaml:"strip_first_directory_from_toc"` StripFirstDirectoryFromToc bool `yaml:"strip_first_directory_from_toc"`
SortPathMode int `yaml:"sort_path_mode"` SortPathMode int `yaml:"sort_path_mode"`
ForegroundColor string `yaml:"foreground_color"`
BackgroundColor string `yaml:"background_color"`
// Default Config // Default Config
Show bool `yaml:"-"` Show bool `yaml:"-"`
@ -65,7 +62,6 @@ func New() *Options {
return &Options{ return &Options{
Profile: "", Profile: "",
Quality: 85, Quality: 85,
Grayscale: true,
Crop: true, Crop: true,
CropRatioLeft: 1, CropRatioLeft: 1,
CropRatioUp: 1, CropRatioUp: 1,
@ -81,34 +77,32 @@ func New() *Options {
LimitMb: 0, LimitMb: 0,
StripFirstDirectoryFromToc: false, StripFirstDirectoryFromToc: false,
SortPathMode: 1, SortPathMode: 1,
ForegroundColor: "000",
BackgroundColor: "FFF",
profiles: profiles.New(), profiles: profiles.New(),
} }
} }
func (o *Options) Header() string { func (o *Options) Header() string {
return "Go Comic Converter\n\nOptions:" return `Go Comic Converter
Options:`
} }
func (o *Options) String() string { func (o *Options) String() string {
var b strings.Builder return fmt.Sprintf(`%s
b.WriteString(o.Header()) Input : %s
for _, v := range []struct { Output : %s
K string Author : %s
V any Title : %s
}{ Workers : %d%s
{"Input", o.Input}, `,
{"Output", o.Output}, o.Header(),
{"Author", o.Author}, o.Input,
{"Title", o.Title}, o.Output,
{"Workers", o.Workers}, o.Author,
} { o.Title,
b.WriteString(fmt.Sprintf("\n %-26s: %v", v.K, v.V)) o.Workers,
} o.ShowConfig(),
b.WriteString(o.ShowConfig()) )
b.WriteRune('\n')
return b.String()
} }
// Config file: ~/.go-comic-converter.yaml // Config file: ~/.go-comic-converter.yaml
@ -167,34 +161,40 @@ func (o *Options) ShowConfig() string {
sortpathmode = "path=alphanum, file=alphanum" sortpathmode = "path=alphanum, file=alphanum"
} }
var b strings.Builder return fmt.Sprintf(`
for _, v := range []struct { Profile : %s
K string ViewRatio : 1:%s
V any View : %s
}{ Quality : %d
{"Profile", profileDesc}, Crop : %v
{"ViewRatio", fmt.Sprintf("1:%s", strings.TrimRight(fmt.Sprintf("%f", profiles.PerfectRatio), "0"))}, CropRatio : %d Left - %d Up - %d Right - %d Bottom
{"View", viewDesc}, Brightness : %d
{"Quality", o.Quality}, Contrast : %d
{"Grayscale", o.Grayscale}, AutoRotate : %v
{"Crop", o.Crop}, AutoSplitDoublePage : %v
{"CropRatio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom)}, NoBlankImage : %v
{"Brightness", o.Brightness}, Manga : %v
{"Contrast", o.Contrast}, HasCover : %v
{"AutoRotate", o.AutoRotate}, LimitMb : %s
{"AutoSplitDoublePage", o.AutoSplitDoublePage}, StripFirstDirectoryFromToc: %v
{"NoBlankImage", o.NoBlankImage}, SortPathMode : %s`,
{"Manga", o.Manga}, profileDesc,
{"HasCover", o.HasCover}, strings.TrimRight(fmt.Sprintf("%f", profiles.PerfectRatio), "0"),
{"LimitMb", limitmb}, viewDesc,
{"StripFirstDirectoryFromToc", o.StripFirstDirectoryFromToc}, o.Quality,
{"SortPathMode", sortpathmode}, o.Crop,
{"Foreground Color", fmt.Sprintf("#%s", o.ForegroundColor)}, o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom,
{"Background Color", fmt.Sprintf("#%s", o.BackgroundColor)}, o.Brightness,
} { o.Contrast,
b.WriteString(fmt.Sprintf("\n %-26s: %v", v.K, v.V)) o.AutoRotate,
} o.AutoSplitDoublePage,
return b.String() o.NoBlankImage,
o.Manga,
o.HasCover,
limitmb,
o.StripFirstDirectoryFromToc,
sortpathmode,
)
} }
// reset all settings to default value // reset all settings to default value

View File

@ -16,7 +16,7 @@ type Profile struct {
} }
// Recommended ratio of image for perfect rendering Portrait or Landscape. // Recommended ratio of image for perfect rendering Portrait or Landscape.
const PerfectRatio = 1.6 const PerfectRatio = 1.5
// Compute best dimension based on device size // Compute best dimension based on device size
func (p Profile) PerfectDim() (int, int) { func (p Profile) PerfectDim() (int, int) {
@ -60,8 +60,6 @@ func New() Profiles {
{"KoF", "Kobo Forma", 1440, 1920}, {"KoF", "Kobo Forma", 1440, 1920},
{"KoS", "Kobo Sage", 1440, 1920}, {"KoS", "Kobo Sage", 1440, 1920},
{"KoE", "Kobo Elipsa", 1404, 1872}, {"KoE", "Kobo Elipsa", 1404, 1872},
// High Resolution for Tablette
{"HR", "High Resolution", 2400, 3840},
} }
} }

View File

@ -77,7 +77,7 @@ func (e *ePub) writeImage(wz *epubzip.EPUBZip, img *epubimage.Image, zipImg *zip
"Title": fmt.Sprintf("Image %d Part %d", img.Id, img.Part), "Title": fmt.Sprintf("Image %d Part %d", img.Id, img.Part),
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height), "ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
"ImagePath": img.ImgPath(), "ImagePath": img.ImgPath(),
"ImageStyle": img.ImgStyle(e.Image.View.Width, e.Image.View.Height, ""), "ImageStyle": img.ImgStyle(e.Image.View.Width, e.Image.View.Height, e.Image.Manga),
})), })),
) )
if err == nil { if err == nil {
@ -251,10 +251,6 @@ func (e *ePub) Write() error {
if totalParts > 1 { if totalParts > 1 {
title = fmt.Sprintf("%s [%d/%d]", title, i+1, totalParts) title = fmt.Sprintf("%s [%d/%d]", title, i+1, totalParts)
} }
titleAlign := "left:0"
if e.Image.Manga {
titleAlign = "right:0"
}
content := []zipContent{ content := []zipContent{
{"META-INF/container.xml", epubtemplates.Container}, {"META-INF/container.xml", epubtemplates.Container},
@ -273,7 +269,8 @@ func (e *ePub) Write() error {
})}, })},
{"OEBPS/toc.xhtml", epubtemplates.Toc(title, e.StripFirstDirectoryFromToc, part.Images)}, {"OEBPS/toc.xhtml", epubtemplates.Toc(title, e.StripFirstDirectoryFromToc, part.Images)},
{"OEBPS/Text/style.css", e.render(epubtemplates.Style, map[string]any{ {"OEBPS/Text/style.css", e.render(epubtemplates.Style, map[string]any{
"View": e.Image.View, "PageWidth": e.Image.View.Width,
"PageHeight": e.Image.View.Height,
})}, })},
{"OEBPS/Text/space_title.xhtml", e.render(epubtemplates.Blank, map[string]any{ {"OEBPS/Text/space_title.xhtml", e.render(epubtemplates.Blank, map[string]any{
"Title": "Blank Page Title", "Title": "Blank Page Title",
@ -283,7 +280,7 @@ func (e *ePub) Write() error {
"Title": title, "Title": title,
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height), "ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
"ImagePath": "Images/title.jpg", "ImagePath": "Images/title.jpg",
"ImageStyle": part.Cover.ImgStyle(e.Image.View.Width, e.Image.View.Height, titleAlign), "ImageStyle": part.Cover.ImgStyle(e.Image.View.Width, e.Image.View.Height, e.Image.Manga),
})}, })},
} }

View File

@ -19,7 +19,6 @@ type Image struct {
DoublePage bool DoublePage bool
Path string Path string
Name string Name string
Position string
} }
// key name of the blank plage after the image // key name of the blank plage after the image
@ -71,17 +70,24 @@ func (i *Image) EPUBImgPath() string {
// //
// center by default. // center by default.
// align to left or right if it's part of the splitted double page. // align to left or right if it's part of the splitted double page.
func (i *Image) ImgStyle(viewWidth, viewHeight int, align string) string { func (i *Image) ImgStyle(viewWidth, viewHeight int, manga bool) string {
marginW, marginH := float64(viewWidth-i.Width)/2, float64(viewHeight-i.Height)/2 marginW, marginH := float64(viewWidth-i.Width)/2, float64(viewHeight-i.Height)/2
left, top := marginW*100/float64(viewWidth), marginH*100/float64(viewHeight)
if align == "" { var align string
switch i.Position { switch i.Part {
case "rendition:page-spread-left": case 0:
align = "right:0" align = fmt.Sprintf("left:%.2f%%", left)
case "rendition:page-spread-right": case 1:
if manga {
align = "left:0"
} else {
align = "right:0"
}
case 2:
if manga {
align = "right:0"
} else {
align = "left:0" align = "left:0"
default:
align = fmt.Sprintf("left:%.2f%%", marginW*100/float64(viewWidth))
} }
} }
@ -89,7 +95,7 @@ func (i *Image) ImgStyle(viewWidth, viewHeight int, align string) string {
"width:%dpx; height:%dpx; top:%.2f%%; %s;", "width:%dpx; height:%dpx; top:%.2f%%; %s;",
i.Width, i.Width,
i.Height, i.Height,
marginH*100/float64(viewHeight), top,
align, align,
) )
} }

View File

@ -6,7 +6,6 @@ package epubimageprocessor
import ( import (
"fmt" "fmt"
"image" "image"
"image/draw"
"os" "os"
"sync" "sync"
@ -127,37 +126,6 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
return images, nil return images, nil
} }
func (e *EPUBImageProcessor) createImage(src image.Image, r image.Rectangle) draw.Image {
if e.Options.Image.GrayScale {
return image.NewGray(r)
}
switch t := src.(type) {
case *image.Gray:
return image.NewGray(r)
case *image.Gray16:
return image.NewGray16(r)
case *image.RGBA:
return image.NewRGBA(r)
case *image.RGBA64:
return image.NewRGBA64(r)
case *image.NRGBA:
return image.NewNRGBA(r)
case *image.NRGBA64:
return image.NewNRGBA64(r)
case *image.Alpha:
return image.NewAlpha(r)
case *image.Alpha16:
return image.NewAlpha16(r)
case *image.CMYK:
return image.NewCMYK(r)
case *image.Paletted:
return image.NewPaletted(r, t.Palette)
default:
return image.NewNRGBA64(r)
}
}
// transform image into 1 or 3 images // transform image into 1 or 3 images
// only doublepage with autosplit has 3 versions // only doublepage with autosplit has 3 versions
func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.Image { func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.Image {
@ -209,7 +177,7 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
// convert // convert
{ {
g := gift.New(filters...) g := gift.New(filters...)
dst := e.createImage(src, g.Bounds(src.Bounds())) dst := image.NewGray(g.Bounds(src.Bounds()))
g.Draw(dst, src) g.Draw(dst, src)
images = append(images, dst) images = append(images, dst)
} }
@ -236,7 +204,7 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
epubimagefilters.CropSplitDoublePage(b), epubimagefilters.CropSplitDoublePage(b),
epubimagefilters.Resize(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling), epubimagefilters.Resize(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling),
) )
dst := e.createImage(src, g.Bounds(src.Bounds())) dst := image.NewGray(g.Bounds(src.Bounds()))
g.Draw(dst, src) g.Draw(dst, src)
images = append(images, dst) images = append(images, dst)
} }
@ -245,11 +213,11 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
} }
// create a title page with the cover // create a title page with the cover
func (e *EPUBImageProcessor) CoverTitleData(src image.Image, title string) (*epubzip.ZipImage, error) { func (e *EPUBImageProcessor) CoverTitleData(img image.Image, title string) (*epubzip.ZipImage, error) {
// Create a blur version of the cover // Create a blur version of the cover
g := gift.New(epubimagefilters.CoverTitle(title)) g := gift.New(epubimagefilters.CoverTitle(title))
dst := e.createImage(src, g.Bounds(src.Bounds())) dst := image.NewGray(g.Bounds(img.Bounds()))
g.Draw(dst, src) g.Draw(dst, img)
return epubzip.CompressImage("OEBPS/Images/title.jpg", dst, e.Image.Quality) return epubzip.CompressImage("OEBPS/Images/title.jpg", dst, e.Image.Quality)
} }

View File

@ -10,13 +10,8 @@ type Crop struct {
Left, Up, Right, Bottom int Left, Up, Right, Bottom int
} }
type Color struct {
Foreground, Background string
}
type View struct { type View struct {
Width, Height int Width, Height int
Color Color
} }
type Image struct { type Image struct {
@ -30,7 +25,6 @@ type Image struct {
Manga bool Manga bool
HasCover bool HasCover bool
View *View View *View
GrayScale bool
} }
type Options struct { type Options struct {

View File

@ -195,11 +195,9 @@ func getSpine(o *ContentOptions) []tag {
"", "",
}) })
} }
// register position for style adjustment
img.Position = getSpread(img.DoublePage)
spine = append(spine, tag{ spine = append(spine, tag{
"itemref", "itemref",
tagAttrs{"idref": img.PageKey(), "properties": img.Position}, tagAttrs{"idref": img.PageKey(), "properties": getSpread(img.DoublePage)},
"", "",
}) })
} }

View File

@ -1,12 +1,12 @@
body { body {
color: #{{ .View.Color.Foreground }}; color: #000;
background: #{{ .View.Color.Background }}; background: #FFF;
top: 0; top: 0;
left: 0; left: 0;
margin: 0; margin: 0;
padding: 0; padding: 0;
width: {{ .View.Width }}px; width: {{ .PageWidth }}px;
height: {{ .View.Height }}px; height: {{ .PageHeight }}px;
text-align: center; text-align: center;
} }

View File

@ -114,8 +114,6 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
DryVerbose: cmd.Options.DryVerbose, DryVerbose: cmd.Options.DryVerbose,
Quiet: cmd.Options.Quiet, Quiet: cmd.Options.Quiet,
Image: &epuboptions.Image{ Image: &epuboptions.Image{
Quality: cmd.Options.Quality,
GrayScale: cmd.Options.Grayscale,
Crop: &epuboptions.Crop{ Crop: &epuboptions.Crop{
Enabled: cmd.Options.Crop, Enabled: cmd.Options.Crop,
Left: cmd.Options.CropRatioLeft, Left: cmd.Options.CropRatioLeft,
@ -123,6 +121,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
Right: cmd.Options.CropRatioRight, Right: cmd.Options.CropRatioRight,
Bottom: cmd.Options.CropRatioBottom, Bottom: cmd.Options.CropRatioBottom,
}, },
Quality: cmd.Options.Quality,
Brightness: cmd.Options.Brightness, Brightness: cmd.Options.Brightness,
Contrast: cmd.Options.Contrast, Contrast: cmd.Options.Contrast,
AutoRotate: cmd.Options.AutoRotate, AutoRotate: cmd.Options.AutoRotate,
@ -133,10 +132,6 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
View: &epuboptions.View{ View: &epuboptions.View{
Width: perfectWidth, Width: perfectWidth,
Height: perfectHeight, Height: perfectHeight,
Color: epuboptions.Color{
Foreground: cmd.Options.ForegroundColor,
Background: cmd.Options.BackgroundColor,
},
}, },
}, },
}).Write(); err != nil { }).Write(); err != nil {