Compare commits

..

No commits in common. "2990367e2f77a760d6f6191d0aa0722820d5dd94" and "58ea72ce82b362538a38b004040973f92d51dda9" have entirely different histories.

28 changed files with 357 additions and 374 deletions

View File

@ -2,7 +2,7 @@
Convert CBZ/CBR/Dir into EPUB for e-reader devices (Kindle Devices, ...) Convert CBZ/CBR/Dir into EPUB for e-reader devices (Kindle Devices, ...)
My goal is to make a simple, cross-platform, and fast tool to convert comics into EPUB. My goal is to make a simple, crossplatform, and fast tool to convert comics into EPUB.
EPUB is now support by Amazon through [SendToKindle](https://www.amazon.com/gp/sendtokindle/), by Email or by using the App. So I've made it simple to support the size limit constraint of those services. EPUB is now support by Amazon through [SendToKindle](https://www.amazon.com/gp/sendtokindle/), by Email or by using the App. So I've made it simple to support the size limit constraint of those services.
@ -16,7 +16,7 @@ EPUB is now support by Amazon through [SendToKindle](https://www.amazon.com/gp/s
- Auto contrast - Auto contrast
- Auto rotate (if reader mainly read on portrait) - Auto rotate (if reader mainly read on portrait)
- Auto split double page (for easy read on portrait) - Auto split double page (for easy read on portrait)
- Keep double page if split - Keep double page if splitted
- Remove blank image (empty image is removed) - Remove blank image (empty image is removed)
- Manga or Normal mode - Manga or Normal mode
- Support cover page or not (first page will be taken in that case) - Support cover page or not (first page will be taken in that case)
@ -91,7 +91,7 @@ Convert every supported image files found in the input directory:
$ go-comic-converter -profile SR -input ~/Download/MyComic $ go-comic-converter -profile SR -input ~/Download/MyComic
``` ```
By default, it will output: ~/Download/MyComic.epub By default it will output: ~/Download/MyComic.epub
## Convert CBZ, ZIP, CBR, RAR, PDF ## Convert CBZ, ZIP, CBR, RAR, PDF
@ -101,7 +101,7 @@ Convert every supported image files found in the input directory:
$ go-comic-converter -profile SR -input ~/Download/MyComic.[CBZ,ZIP,CBR,RAR,PDF] $ go-comic-converter -profile SR -input ~/Download/MyComic.[CBZ,ZIP,CBR,RAR,PDF]
``` ```
By default, it will output: ~/Download/MyComic.epub By default it will output: ~/Download/MyComic.epub
## Convert with size limit ## Convert with size limit
@ -130,7 +130,7 @@ If the total is above 1, then the title of the EPUB include:
## Dry run ## Dry run
If you want to preview what will be set during the conversion without running the conversion, then you can use the `-dry` option. If you want to preview what will be set during the convertion without running the conversion, then you can use the `-dry` option.
``` ```
$ go-comic-converter -input ~/Downloads/mymanga.cbr -profile SR -auto -manga -limitmb 200 -dry $ go-comic-converter -input ~/Downloads/mymanga.cbr -profile SR -auto -manga -limitmb 200 -dry
@ -176,7 +176,7 @@ TOC:
## Dry verbose ## Dry verbose
You can choose different way to sort path and files, depending on your source. You can preview the sorted result with the option `dry-verbose` associated with `dry`. You can choose different way to sort path and files, depending of your source. You can preview the sorted result with the option `dry-verbose` associated with `dry`.
The option `sort` allow you to change the sorting order. The option `sort` allow you to change the sorting order.
@ -418,7 +418,7 @@ Explanation:
- `-autocontrast`: automatically improve contrast - `-autocontrast`: automatically improve contrast
- `-manga`: manga mode, read right to left - `-manga`: manga mode, read right to left
- `-limitmb 200`: size limit to 200MB allowing upload from SendToKindle website - `-limitmb 200`: size limit to 200MB allowing upload from SendToKindle website
- `-strip`: remove first level if alone on TOC, as often comics include a main directory with the title - `-strip`: remove first level if alone on TOC, as offen comics include a main directory with the title
- `aspect-ratio`: ensure aspect ratio is 1:1.6, best for kindle devices. - `aspect-ratio`: ensure aspect ratio is 1:1.6, best for kindle devices.
# Help # Help

8
go.mod
View File

@ -21,9 +21,9 @@ require (
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/stretchr/testify v1.8.4 // indirect github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/net v0.21.0 // indirect golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.17.0 // indirect golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.17.0 // indirect golang.org/x/term v0.16.0 // indirect
) )

15
go.sum
View File

@ -29,9 +29,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0 h1:fuFvfwIc+cpySYurvDNTs5LIHXP9Cj3reVRplj9Whv4= github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0 h1:fuFvfwIc+cpySYurvDNTs5LIHXP9Cj3reVRplj9Whv4=
github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0/go.mod h1:Ql3QqeGiYGlPOtYz+F/L7J27spqDcdH9LhDHOrrdsD4= github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0/go.mod h1:Ql3QqeGiYGlPOtYz+F/L7J27spqDcdH9LhDHOrrdsD4=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/schollz/progressbar/v3 v3.14.1 h1:VD+MJPCr4s3wdhTc7OEJ/Z3dAeBzJ7yKH/P4lC5yRTI= github.com/schollz/progressbar/v3 v3.14.1 h1:VD+MJPCr4s3wdhTc7OEJ/Z3dAeBzJ7yKH/P4lC5yRTI=
github.com/schollz/progressbar/v3 v3.14.1/go.mod h1:Zc9xXneTzWXF81TGoqL71u0sBPjULtEHYtj/WVgVy8E= github.com/schollz/progressbar/v3 v3.14.1/go.mod h1:Zc9xXneTzWXF81TGoqL71u0sBPjULtEHYtj/WVgVy8E=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -42,15 +41,15 @@ github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2
github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM= 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 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -1,8 +1,10 @@
// Package converter Helper to parse and prepare options for go-comic-converter. /*
// Converter Helper to parse and prepare options for go-comic-converter.
// It uses goflag with additional feature:
// - Keep original order It use goflag with additional feature:
// - Support section - Keep original order
- Support section
*/
package converter package converter
import ( import (
@ -30,12 +32,12 @@ type Converter struct {
startAt time.Time startAt time.Time
} }
// New Create a new parser // Create a new parser
func New() *Converter { func New() *Converter {
o := options.New() options := options.New()
cmd := flag.NewFlagSet("go-comic-converter", flag.ExitOnError) cmd := flag.NewFlagSet("go-comic-converter", flag.ExitOnError)
conv := &Converter{ conv := &Converter{
Options: o, Options: options,
Cmd: cmd, Cmd: cmd,
order: make([]converterOrder, 0), order: make([]converterOrder, 0),
startAt: time.Now(), startAt: time.Now(),
@ -61,41 +63,41 @@ func New() *Converter {
return conv return conv
} }
// LoadConfig Load default options (config + default) // Load default options (config + default)
func (c *Converter) LoadConfig() error { func (c *Converter) LoadConfig() error {
return c.Options.LoadConfig() return c.Options.LoadConfig()
} }
// AddSection Create a new section of config // Create a new section of config
func (c *Converter) AddSection(section string) { func (c *Converter) AddSection(section string) {
c.order = append(c.order, converterOrderSection{value: section}) c.order = append(c.order, converterOrderSection{value: section})
} }
// AddStringParam Add a string parameter // Add a string parameter
func (c *Converter) AddStringParam(p *string, name string, value string, usage string) { func (c *Converter) AddStringParam(p *string, name string, value string, usage string) {
c.Cmd.StringVar(p, name, value, usage) c.Cmd.StringVar(p, name, value, usage)
c.order = append(c.order, converterOrderName{value: name, isString: true}) c.order = append(c.order, converterOrderName{value: name, isString: true})
} }
// AddIntParam Add an integer parameter // Add an integer parameter
func (c *Converter) AddIntParam(p *int, name string, value int, usage string) { func (c *Converter) AddIntParam(p *int, name string, value int, usage string) {
c.Cmd.IntVar(p, name, value, usage) c.Cmd.IntVar(p, name, value, usage)
c.order = append(c.order, converterOrderName{value: name}) c.order = append(c.order, converterOrderName{value: name})
} }
// AddFloatParam Add an float parameter // Add an float parameter
func (c *Converter) AddFloatParam(p *float64, name string, value float64, usage string) { func (c *Converter) AddFloatParam(p *float64, name string, value float64, usage string) {
c.Cmd.Float64Var(p, name, value, usage) c.Cmd.Float64Var(p, name, value, usage)
c.order = append(c.order, converterOrderName{value: name}) c.order = append(c.order, converterOrderName{value: name})
} }
// AddBoolParam Add a boolean parameter // Add a boolean parameter
func (c *Converter) AddBoolParam(p *bool, name string, value bool, usage string) { func (c *Converter) AddBoolParam(p *bool, name string, value bool, usage string) {
c.Cmd.BoolVar(p, name, value, usage) c.Cmd.BoolVar(p, name, value, usage)
c.order = append(c.order, converterOrderName{value: name}) c.order = append(c.order, converterOrderName{value: name})
} }
// InitParse Initialize the parser with all section and parameter. // Initialize the parser with all section and parameter.
func (c *Converter) InitParse() { func (c *Converter) InitParse() {
c.AddSection("Output") c.AddSection("Output")
c.AddStringParam(&c.Options.Input, "input", "", "Source of comic to convert: directory, cbz, zip, cbr, rar, pdf") c.AddStringParam(&c.Options.Input, "input", "", "Source of comic to convert: directory, cbz, zip, cbr, rar, pdf")
@ -113,25 +115,25 @@ func (c *Converter) InitParse() {
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.")
c.AddIntParam(&c.Options.CropRatioRight, "crop-ratio-right", c.Options.CropRatioRight, "Crop ratio right: ratio of pixels allow to be non blank while cutting on the right.") c.AddIntParam(&c.Options.CropRatioRight, "crop-ratio-right", c.Options.CropRatioRight, "Crop ratio right: ratio of pixels allow to be non blank while cutting on the right.")
c.AddIntParam(&c.Options.CropRatioBottom, "crop-ratio-bottom", c.Options.CropRatioBottom, "Crop ratio bottom: ratio of pixels allow to be non blank while cutting on the bottom.") c.AddIntParam(&c.Options.CropRatioBottom, "crop-ratio-bottom", c.Options.CropRatioBottom, "Crop ratio bottom: ratio of pixels allow to be non blank while cutting on the bottom.")
c.AddIntParam(&c.Options.Brightness, "brightness", c.Options.Brightness, "Brightness readjustment: between -100 and 100, > 0 lighter, < 0 darker") c.AddIntParam(&c.Options.Brightness, "brightness", c.Options.Brightness, "Brightness readjustement: between -100 and 100, > 0 lighter, < 0 darker")
c.AddIntParam(&c.Options.Contrast, "contrast", c.Options.Contrast, "Contrast readjustment: between -100 and 100, > 0 more contrast, < 0 less contrast") c.AddIntParam(&c.Options.Contrast, "contrast", c.Options.Contrast, "Contrast readjustement: between -100 and 100, > 0 more contrast, < 0 less contrast")
c.AddBoolParam(&c.Options.AutoContrast, "autocontrast", c.Options.AutoContrast, "Improve contrast automatically") c.AddBoolParam(&c.Options.AutoContrast, "autocontrast", c.Options.AutoContrast, "Improve contrast automatically")
c.AddBoolParam(&c.Options.AutoRotate, "autorotate", c.Options.AutoRotate, "Auto Rotate page when width > height") c.AddBoolParam(&c.Options.AutoRotate, "autorotate", c.Options.AutoRotate, "Auto Rotate page when width > height")
c.AddBoolParam(&c.Options.AutoSplitDoublePage, "autosplitdoublepage", c.Options.AutoSplitDoublePage, "Auto Split double page when width > height") c.AddBoolParam(&c.Options.AutoSplitDoublePage, "autosplitdoublepage", c.Options.AutoSplitDoublePage, "Auto Split double page when width > height")
c.AddBoolParam(&c.Options.KeepDoublePageIfSplit, "keepdoublepageifsplit", c.Options.KeepDoublePageIfSplit, "Keep the double page if split") c.AddBoolParam(&c.Options.KeepDoublePageIfSplitted, "keepdoublepageifsplitted", c.Options.KeepDoublePageIfSplitted, "Keep the double page if splitted")
c.AddBoolParam(&c.Options.NoBlankImage, "noblankimage", c.Options.NoBlankImage, "Remove blank image") c.AddBoolParam(&c.Options.NoBlankImage, "noblankimage", c.Options.NoBlankImage, "Remove blank image")
c.AddBoolParam(&c.Options.Manga, "manga", c.Options.Manga, "Manga mode (right to left)") c.AddBoolParam(&c.Options.Manga, "manga", c.Options.Manga, "Manga mode (right to left)")
c.AddBoolParam(&c.Options.HasCover, "hascover", c.Options.HasCover, "Has cover. Indicate if your comic have a cover. The first page will be used as a cover and include after the title.") c.AddBoolParam(&c.Options.HasCover, "hascover", c.Options.HasCover, "Has cover. Indicate if your comic have a cover. The first page will be used as a cover and include after the title.")
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 = alphanumeric for path and alpha for file\n2 = alphanumeric 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 hexadecimal format RGB. Black=000, White=FFF") 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 hexadecimal format RGB. Black=000, White=FFF, Light Gray=DDD, Dark Gray=777") 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.AddBoolParam(&c.Options.NoResize, "noresize", c.Options.NoResize, "Do not reduce image size if exceed device size") c.AddBoolParam(&c.Options.NoResize, "noresize", c.Options.NoResize, "Do not reduce image size if exceed device size")
c.AddStringParam(&c.Options.Format, "format", c.Options.Format, "Format of output images: jpeg (lossy), png (lossless)") c.AddStringParam(&c.Options.Format, "format", c.Options.Format, "Format of output images: jpeg (lossy), png (lossless)")
c.AddFloatParam(&c.Options.AspectRatio, "aspect-ratio", c.Options.AspectRatio, "Aspect ratio (height/width) of the output\n -1 = same as device\n 0 = same as source\n1.6 = amazon advice for kindle") c.AddFloatParam(&c.Options.AspectRatio, "aspect-ratio", c.Options.AspectRatio, "Aspect ratio (height/width) of the output\n -1 = same as device\n 0 = same as source\n1.6 = amazon advice for kindle")
c.AddBoolParam(&c.Options.PortraitOnly, "portrait-only", c.Options.PortraitOnly, "Portrait only: force orientation to portrait only.") c.AddBoolParam(&c.Options.PortraitOnly, "portrait-only", c.Options.PortraitOnly, "Portrait only: force orientation to portrait only.")
c.AddIntParam(&c.Options.TitlePage, "titlepage", c.Options.TitlePage, "Title page\n0 = never\n1 = always\n2 = only if epub is split") c.AddIntParam(&c.Options.TitlePage, "titlepage", c.Options.TitlePage, "Title page\n0 = never\n1 = always\n2 = only if epub is splitted")
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")
@ -159,7 +161,7 @@ func (c *Converter) InitParse() {
c.AddBoolParam(&c.Options.Help, "help", false, "Show this help message") c.AddBoolParam(&c.Options.Help, "help", false, "Show this help message")
} }
// Usage Customize version of FlagSet.PrintDefaults // Customize version of FlagSet.PrintDefaults
func (c *Converter) Usage(isString bool, f *flag.Flag) string { func (c *Converter) Usage(isString bool, f *flag.Flag) string {
var b strings.Builder var b strings.Builder
fmt.Fprintf(&b, " -%s", f.Name) // Two spaces before -; see next two comments. fmt.Fprintf(&b, " -%s", f.Name) // Two spaces before -; see next two comments.
@ -270,11 +272,11 @@ func (c *Converter) Parse() {
if c.Options.AppleBookCompatibility { if c.Options.AppleBookCompatibility {
c.Options.AutoSplitDoublePage = true c.Options.AutoSplitDoublePage = true
c.Options.KeepDoublePageIfSplit = false c.Options.KeepDoublePageIfSplitted = false
} }
} }
// Validate Check parameters // Check parameters
func (c *Converter) Validate() error { func (c *Converter) Validate() error {
// Check input // Check input
if c.Options.Input == "" { if c.Options.Input == "" {
@ -361,11 +363,11 @@ func (c *Converter) Validate() error {
// Color // Color
colorRegex := regexp.MustCompile("^[0-9A-F]{3}$") colorRegex := regexp.MustCompile("^[0-9A-F]{3}$")
if !colorRegex.MatchString(c.Options.ForegroundColor) { if !colorRegex.MatchString(c.Options.ForegroundColor) {
return errors.New("foreground color must have color format in hexadecimal: [0-9A-F]{3}") return errors.New("foreground color must have color format in hexa: [0-9A-F]{3}")
} }
if !colorRegex.MatchString(c.Options.BackgroundColor) { if !colorRegex.MatchString(c.Options.BackgroundColor) {
return errors.New("background color must have color format in hexadecimal: [0-9A-F]{3}") return errors.New("background color must have color format in hexa: [0-9A-F]{3}")
} }
// Format // Format
@ -391,7 +393,7 @@ func (c *Converter) Validate() error {
return nil return nil
} }
// Fatal Helper to show usage, err and exit 1 // Helper to show usage, err and exit 1
func (c *Converter) Fatal(err error) { func (c *Converter) Fatal(err error) {
c.Cmd.Usage() c.Cmd.Usage()
fmt.Fprintf(os.Stderr, "\nError: %s\n", err) fmt.Fprintf(os.Stderr, "\nError: %s\n", err)

View File

@ -1,4 +1,6 @@
// Package options manage options with default value from config. /*
Manage options with default value from config.
*/
package options package options
import ( import (
@ -34,7 +36,7 @@ type Options struct {
AutoContrast bool `yaml:"auto_contrast"` AutoContrast bool `yaml:"auto_contrast"`
AutoRotate bool `yaml:"auto_rotate"` AutoRotate bool `yaml:"auto_rotate"`
AutoSplitDoublePage bool `yaml:"auto_split_double_page"` AutoSplitDoublePage bool `yaml:"auto_split_double_page"`
KeepDoublePageIfSplit bool `yaml:"keep_double_page_if_split"` KeepDoublePageIfSplitted bool `yaml:"keep_double_page_if_splitted"`
NoBlankImage bool `yaml:"no_blank_image"` NoBlankImage bool `yaml:"no_blank_image"`
Manga bool `yaml:"manga"` Manga bool `yaml:"manga"`
HasCover bool `yaml:"has_cover"` HasCover bool `yaml:"has_cover"`
@ -76,26 +78,26 @@ type Options struct {
profiles profiles.Profiles profiles profiles.Profiles
} }
// New Initialize default options. // Initialize default options.
func New() *Options { func New() *Options {
return &Options{ return &Options{
Profile: "SR", Profile: "SR",
Quality: 85, Quality: 85,
Grayscale: true, Grayscale: true,
Crop: true, Crop: true,
CropRatioLeft: 1, CropRatioLeft: 1,
CropRatioUp: 1, CropRatioUp: 1,
CropRatioRight: 1, CropRatioRight: 1,
CropRatioBottom: 3, CropRatioBottom: 3,
NoBlankImage: true, NoBlankImage: true,
HasCover: true, HasCover: true,
KeepDoublePageIfSplit: true, KeepDoublePageIfSplitted: true,
SortPathMode: 1, SortPathMode: 1,
ForegroundColor: "000", ForegroundColor: "000",
BackgroundColor: "FFF", BackgroundColor: "FFF",
Format: "jpeg", Format: "jpeg",
TitlePage: 1, TitlePage: 1,
profiles: profiles.New(), profiles: profiles.New(),
} }
} }
@ -171,7 +173,7 @@ func (o *Options) MarshalJSON() ([]byte, error) {
if o.PortraitOnly || !o.AppleBookCompatibility { if o.PortraitOnly || !o.AppleBookCompatibility {
out["autosplitdoublepage"] = o.AutoSplitDoublePage out["autosplitdoublepage"] = o.AutoSplitDoublePage
if o.AutoSplitDoublePage { if o.AutoSplitDoublePage {
out["keepdoublepageifsplit"] = o.KeepDoublePageIfSplit out["keepdoublepageifsplitted"] = o.KeepDoublePageIfSplitted
} }
} }
if o.LimitMb != 0 { if o.LimitMb != 0 {
@ -183,13 +185,13 @@ func (o *Options) MarshalJSON() ([]byte, error) {
return json.Marshal(out) return json.Marshal(out)
} }
// FileName Config file: ~/.go-comic-converter.yaml // Config file: ~/.go-comic-converter.yaml
func (o *Options) FileName() string { func (o *Options) FileName() string {
home, _ := os.UserHomeDir() home, _ := os.UserHomeDir()
return filepath.Join(home, ".go-comic-converter.yaml") return filepath.Join(home, ".go-comic-converter.yaml")
} }
// LoadConfig Load config files // Load config files
func (o *Options) LoadConfig() error { func (o *Options) LoadConfig() error {
f, err := os.Open(o.FileName()) f, err := os.Open(o.FileName())
if err != nil { if err != nil {
@ -204,7 +206,7 @@ func (o *Options) LoadConfig() error {
return nil return nil
} }
// ShowConfig Get current settings for fields that can be saved // Get current settings for fields that can be saved
func (o *Options) ShowConfig() string { func (o *Options) ShowConfig() string {
var profileDesc string var profileDesc string
profile := o.GetProfile() profile := o.GetProfile()
@ -223,9 +225,9 @@ func (o *Options) ShowConfig() string {
case 0: case 0:
sortpathmode = "path=alpha, file=alpha" sortpathmode = "path=alpha, file=alpha"
case 1: case 1:
sortpathmode = "path=alphanumeric, file=alpha" sortpathmode = "path=alphanum, file=alpha"
case 2: case 2:
sortpathmode = "path=alphanumeric, file=alphanumeric" sortpathmode = "path=alphanum, file=alphanum"
} }
aspectRatio := "auto" aspectRatio := "auto"
@ -242,7 +244,7 @@ func (o *Options) ShowConfig() string {
case 1: case 1:
titlePage = "always" titlePage = "always"
case 2: case 2:
titlePage = "when epub is split" titlePage = "when epub is splitted"
} }
grayscaleMode := "normal" grayscaleMode := "normal"
@ -263,28 +265,28 @@ func (o *Options) ShowConfig() string {
{"Format", o.Format, true}, {"Format", o.Format, true},
{"Quality", o.Quality, o.Format == "jpeg"}, {"Quality", o.Quality, o.Format == "jpeg"},
{"Grayscale", o.Grayscale, true}, {"Grayscale", o.Grayscale, true},
{"Grayscale mode", grayscaleMode, o.Grayscale}, {"Grayscale Mode", grayscaleMode, o.Grayscale},
{"Crop", o.Crop, true}, {"Crop", o.Crop, true},
{"Crop ratio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom), o.Crop}, {"Crop Ratio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom), o.Crop},
{"Brightness", o.Brightness, o.Brightness != 0}, {"Brightness", o.Brightness, o.Brightness != 0},
{"Contrast", o.Contrast, o.Contrast != 0}, {"Contrast", o.Contrast, o.Contrast != 0},
{"Auto contrast", o.AutoContrast, true}, {"Auto Contrast", o.AutoContrast, true},
{"Auto rotate", o.AutoRotate, true}, {"Auto Rotate", o.AutoRotate, true},
{"Auto split double page", o.AutoSplitDoublePage, o.PortraitOnly || !o.AppleBookCompatibility}, {"Auto Split DoublePage", o.AutoSplitDoublePage, o.PortraitOnly || !o.AppleBookCompatibility},
{"Keep double page if split", o.KeepDoublePageIfSplit, (o.PortraitOnly || !o.AppleBookCompatibility) && o.AutoSplitDoublePage}, {"Keep DoublePage If Splitted", o.KeepDoublePageIfSplitted, (o.PortraitOnly || !o.AppleBookCompatibility) && o.AutoSplitDoublePage},
{"No blank image", o.NoBlankImage, true}, {"No Blank Image", o.NoBlankImage, true},
{"Manga", o.Manga, true}, {"Manga", o.Manga, true},
{"Has cover", o.HasCover, true}, {"Has Cover", o.HasCover, true},
{"Limit", fmt.Sprintf("%d Mb", o.LimitMb), o.LimitMb != 0}, {"Limit", fmt.Sprintf("%d Mb", o.LimitMb), o.LimitMb != 0},
{"Strip first directory from toc", o.StripFirstDirectoryFromToc, true}, {"Strip First Directory From Toc", o.StripFirstDirectoryFromToc, true},
{"Sort path mode", sortpathmode, true}, {"Sort Path Mode", sortpathmode, true},
{"Foreground color", fmt.Sprintf("#%s", o.ForegroundColor), true}, {"Foreground Color", fmt.Sprintf("#%s", o.ForegroundColor), true},
{"Background color", fmt.Sprintf("#%s", o.BackgroundColor), true}, {"Background Color", fmt.Sprintf("#%s", o.BackgroundColor), true},
{"Resize", !o.NoResize, true}, {"Resize", !o.NoResize, true},
{"Aspect ratio", aspectRatio, true}, {"Aspect Ratio", aspectRatio, true},
{"Portrait only", o.PortraitOnly, true}, {"Portrait Only", o.PortraitOnly, true},
{"Title page", titlePage, true}, {"Title Page", titlePage, true},
{"Apple book compatibility", o.AppleBookCompatibility, !o.PortraitOnly}, {"Apple Book Compatibility", o.AppleBookCompatibility, !o.PortraitOnly},
} { } {
if v.Condition { if v.Condition {
b.WriteString(fmt.Sprintf("\n %-32s: %v", v.Key, v.Value)) b.WriteString(fmt.Sprintf("\n %-32s: %v", v.Key, v.Value))
@ -293,13 +295,13 @@ func (o *Options) ShowConfig() string {
return b.String() return b.String()
} }
// ResetConfig reset all settings to default value // reset all settings to default value
func (o *Options) ResetConfig() error { func (o *Options) ResetConfig() error {
New().SaveConfig() New().SaveConfig()
return o.LoadConfig() return o.LoadConfig()
} }
// SaveConfig save all current settings as default value // save all current settings as futur default value
func (o *Options) SaveConfig() error { func (o *Options) SaveConfig() error {
f, err := os.Create(o.FileName()) f, err := os.Create(o.FileName())
if err != nil { if err != nil {
@ -309,12 +311,12 @@ func (o *Options) SaveConfig() error {
return yaml.NewEncoder(f).Encode(o) return yaml.NewEncoder(f).Encode(o)
} }
// GetProfile shortcut to get current profile // shortcut to get current profile
func (o *Options) GetProfile() *profiles.Profile { func (o *Options) GetProfile() *profiles.Profile {
return o.profiles.Get(o.Profile) return o.profiles.Get(o.Profile)
} }
// AvailableProfiles all available profiles // all available profiles
func (o *Options) AvailableProfiles() string { func (o *Options) AvailableProfiles() string {
return o.profiles.String() return o.profiles.String()
} }

View File

@ -1,4 +1,6 @@
// Package profiles manage supported profiles for go-comic-converter. /*
Manage supported profiles for go-comic-converter.
*/
package profiles package profiles
import ( import (
@ -15,10 +17,10 @@ type Profile struct {
type Profiles []Profile type Profiles []Profile
// New Initialize list of all supported profiles. // Initialize list of all supported profiles.
func New() Profiles { func New() Profiles {
return []Profile{ return []Profile{
// High Resolution for Tablet // High Resolution for Tablette
{"HR", "High Resolution", 2400, 3840}, {"HR", "High Resolution", 2400, 3840},
{"SR", "Standard Resolution", 1200, 1920}, {"SR", "Standard Resolution", 1200, 1920},
//Kindle //Kindle
@ -66,7 +68,7 @@ func (p Profiles) String() string {
return strings.Join(s, "\n") return strings.Join(s, "\n")
} }
// Get Lookup profile by code // Lookup profile by code
func (p Profiles) Get(name string) *Profile { func (p Profiles) Get(name string) *Profile {
for _, profile := range p { for _, profile := range p {
if profile.Code == name { if profile.Code == name {

View File

@ -1,4 +1,6 @@
// Package epub Tools to create EPUB from images. /*
Tools to create EPUB from images.
*/
package epub package epub
import ( import (
@ -23,7 +25,7 @@ import (
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
) )
type EPub struct { type ePub struct {
*epuboptions.Options *epuboptions.Options
UID string UID string
Publisher string Publisher string
@ -39,8 +41,8 @@ type epubPart struct {
Reader *zip.ReadCloser Reader *zip.ReadCloser
} }
// New initialize EPUB // initialize EPUB
func New(options *epuboptions.Options) *EPub { func New(options *epuboptions.Options) *ePub {
uid := uuid.Must(uuid.NewV4()) uid := uuid.Must(uuid.NewV4())
tmpl := template.New("parser") tmpl := template.New("parser")
tmpl.Funcs(template.FuncMap{ tmpl.Funcs(template.FuncMap{
@ -48,7 +50,7 @@ func New(options *epuboptions.Options) *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{
Options: options, Options: options,
UID: uid.String(), UID: uid.String(),
Publisher: "GO Comic Converter", Publisher: "GO Comic Converter",
@ -59,7 +61,7 @@ func New(options *epuboptions.Options) *EPub {
} }
// render templates // 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 var result strings.Builder
tmpl := template.Must(e.templateProcessor.Parse(templateString)) tmpl := template.Must(e.templateProcessor.Parse(templateString))
if err := tmpl.Execute(&result, data); err != nil { if err := tmpl.Execute(&result, data); err != nil {
@ -69,7 +71,7 @@ func (e *EPub) render(templateString string, data map[string]any) string {
} }
// write image to the zip // 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.Image, zipImg *zip.File) error {
err := wz.WriteContent( err := wz.WriteContent(
img.EPUBPagePath(), img.EPUBPagePath(),
[]byte(e.render(epubtemplates.Text, map[string]any{ []byte(e.render(epubtemplates.Text, map[string]any{
@ -87,7 +89,7 @@ func (e *EPub) writeImage(wz *epubzip.EPUBZip, img *epubimage.Image, zipImg *zip
} }
// write blank page // write blank page
func (e *EPub) writeBlank(wz *epubzip.EPUBZip, img *epubimage.Image) error { func (e *ePub) writeBlank(wz *epubzip.EPUBZip, img *epubimage.Image) error {
return wz.WriteContent( return wz.WriteContent(
img.EPUBSpacePath(), img.EPUBSpacePath(),
[]byte(e.render(epubtemplates.Blank, map[string]any{ []byte(e.render(epubtemplates.Blank, map[string]any{
@ -98,7 +100,7 @@ func (e *EPub) writeBlank(wz *epubzip.EPUBZip, img *epubimage.Image) error {
} }
// write title image // 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.Image, part, totalParts int) error {
title := "Cover" title := "Cover"
text := "" text := ""
if totalParts > 1 { if totalParts > 1 {
@ -141,7 +143,7 @@ func (e *EPub) writeCoverImage(wz *epubzip.EPUBZip, img *epubimage.Image, part,
} }
// write title image // 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.Image, title string) error {
titleAlign := "" titleAlign := ""
if !e.Image.View.PortraitOnly { if !e.Image.View.PortraitOnly {
if e.Image.Manga { if e.Image.Manga {
@ -197,7 +199,7 @@ func (e *EPub) writeTitleImage(wz *epubzip.EPUBZip, img *epubimage.Image, title
} }
// extract image and split it into part // 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.EPUBZipStorageImageReader, err error) {
images, err := e.imageProcessor.Load() images, err := e.imageProcessor.Load()
if err != nil { if err != nil {
@ -226,7 +228,7 @@ func (e *EPub) getParts() (parts []*epubPart, imgStorage *epubzip.StorageImageRe
return parts, nil, nil return parts, nil, nil
} }
imgStorage, err = epubzip.NewStorageImageReader(e.ImgStorage()) imgStorage, err = epubzip.NewEPUBZipStorageImageReader(e.ImgStorage())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -268,24 +270,24 @@ func (e *EPub) getParts() (parts []*epubPart, imgStorage *epubzip.StorageImageRe
// create a tree from the directories. // create a tree from the directories.
// //
// this is used to simulate the toc. // this is used to simulate the toc.
func (e *EPub) getTree(images []*epubimage.Image, skipFiles bool) string { func (e *ePub) getTree(images []*epubimage.Image, skip_files bool) string {
t := epubtree.New() t := epubtree.New()
for _, img := range images { for _, img := range images {
if skipFiles { if skip_files {
t.Add(img.Path) t.Add(img.Path)
} else { } else {
t.Add(filepath.Join(img.Path, img.Name)) t.Add(filepath.Join(img.Path, img.Name))
} }
} }
c := t.Root() c := t.Root()
if skipFiles && e.StripFirstDirectoryFromToc && c.ChildCount() == 1 { if skip_files && e.StripFirstDirectoryFromToc && len(c.Children) == 1 {
c = c.FirstChild() c = c.Children[0]
} }
return c.WriteString("") return c.WriteString("")
} }
func (e *EPub) computeAspectRatio(epubParts []*epubPart) float64 { func (e *ePub) computeAspectRatio(epubParts []*epubPart) float64 {
var ( var (
bestAspectRatio float64 bestAspectRatio float64
bestAspectRatioCount int bestAspectRatioCount int
@ -312,7 +314,7 @@ func (e *EPub) computeAspectRatio(epubParts []*epubPart) float64 {
return bestAspectRatio return bestAspectRatio
} }
func (e *EPub) computeViewPort(epubParts []*epubPart) { func (e *ePub) computeViewPort(epubParts []*epubPart) {
if e.Image.View.AspectRatio == -1 { if e.Image.View.AspectRatio == -1 {
return //keep device size return //keep device size
} }
@ -331,86 +333,13 @@ func (e *EPub) computeViewPort(epubParts []*epubPart) {
} }
} }
func (e *EPub) writePart(path string, currentPart, totalParts int, part *epubPart, imgStorage *epubzip.StorageImageReader) error { // create the zip
hasTitlePage := e.TitlePage == 1 || (e.TitlePage == 2 && totalParts > 1) func (e *ePub) Write() error {
wz, err := epubzip.New(path)
if err != nil {
return err
}
defer wz.Close()
title := e.Title
if totalParts > 1 {
title = fmt.Sprintf("%s [%d/%d]", title, currentPart, totalParts)
}
type zipContent struct { type zipContent struct {
Name string Name string
Content string Content string
} }
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{
Title: title,
HasTitlePage: hasTitlePage,
UID: e.UID,
Author: e.Author,
Publisher: e.Publisher,
UpdatedAt: e.UpdatedAt,
ImageOptions: e.Image,
Cover: part.Cover,
Images: part.Images,
Current: currentPart,
Total: totalParts,
})},
{"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,
})},
}
if err = wz.WriteMagic(); err != nil {
return err
}
for _, c := range content {
if err := wz.WriteContent(c.Name, []byte(c.Content)); err != nil {
return err
}
}
if err = e.writeCoverImage(wz, part.Cover, currentPart, totalParts); err != nil {
return err
}
if hasTitlePage {
if err = e.writeTitleImage(wz, part.Cover, title); err != nil {
return err
}
}
lastImage := part.Images[len(part.Images)-1]
for _, img := range part.Images {
if err := e.writeImage(wz, img, imgStorage.Get(img.EPUBImgPath())); err != nil {
return err
}
// Double Page or Last Image that is not a double page
if !e.Image.View.PortraitOnly &&
(img.DoublePage ||
(!e.Image.KeepDoublePageIfSplit && img.Part == 1) ||
(img.Part == 0 && img == lastImage)) {
if err := e.writeBlank(wz, img); err != nil {
return err
}
}
}
return nil
}
// create the zip
func (e *EPub) Write() error {
epubParts, imgStorage, err := e.getParts() epubParts, imgStorage, err := e.getParts()
if err != nil { if err != nil {
return err return err
@ -444,6 +373,7 @@ func (e *EPub) Write() error {
}) })
e.computeViewPort(epubParts) e.computeViewPort(epubParts)
hasTitlePage := e.TitlePage == 1 || (e.TitlePage == 2 && totalParts > 1)
for i, part := range epubParts { for i, part := range epubParts {
ext := filepath.Ext(e.Output) ext := filepath.Ext(e.Output)
suffix := "" suffix := ""
@ -454,17 +384,74 @@ func (e *EPub) Write() error {
} }
path := fmt.Sprintf("%s%s%s", e.Output[0:len(e.Output)-len(ext)], suffix, ext) path := fmt.Sprintf("%s%s%s", e.Output[0:len(e.Output)-len(ext)], suffix, ext)
wz, err := epubzip.New(path)
if err != nil {
return err
}
defer wz.Close()
if err := e.writePart( title := e.Title
path, if totalParts > 1 {
i+1, title = fmt.Sprintf("%s [%d/%d]", title, i+1, totalParts)
totalParts, }
part,
imgStorage, content := []zipContent{
); err != nil { {"META-INF/container.xml", epubtemplates.Container},
{"META-INF/com.apple.ibooks.display-options.xml", epubtemplates.AppleBooks},
{"OEBPS/content.opf", epubtemplates.Content(&epubtemplates.ContentOptions{
Title: title,
HasTitlePage: hasTitlePage,
UID: e.UID,
Author: e.Author,
Publisher: e.Publisher,
UpdatedAt: e.UpdatedAt,
ImageOptions: e.Image,
Cover: part.Cover,
Images: part.Images,
Current: i + 1,
Total: totalParts,
})},
{"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,
})},
}
if err = wz.WriteMagic(); err != nil {
return err
}
for _, c := range content {
if err := wz.WriteContent(c.Name, []byte(c.Content)); err != nil {
return err
}
}
if err = e.writeCoverImage(wz, part.Cover, i+1, totalParts); err != nil {
return err return err
} }
if hasTitlePage {
if err = e.writeTitleImage(wz, part.Cover, title); err != nil {
return err
}
}
lastImage := part.Images[len(part.Images)-1]
for _, img := range part.Images {
if err := e.writeImage(wz, img, imgStorage.Get(img.EPUBImgPath())); err != nil {
return err
}
// Double Page or Last Image that is not a double page
if !e.Image.View.PortraitOnly &&
(img.DoublePage ||
(!e.Image.KeepDoublePageIfSplitted && img.Part == 1) ||
(img.Part == 0 && img == lastImage)) {
if err := e.writeBlank(wz, img); err != nil {
return err
}
}
}
bar.Add(1) bar.Add(1)
} }
bar.Close() bar.Close()

View File

@ -1,4 +1,6 @@
// Package epubimage Image helpers to transform image. /*
Image helpers to transform image.
*/
package epubimage package epubimage
import ( import (
@ -23,60 +25,60 @@ type Image struct {
Error error Error error
} }
// SpaceKey key name of the blank page after the image // key name of the blank plage after the image
func (i *Image) SpaceKey() string { func (i *Image) SpaceKey() string {
return fmt.Sprintf("space_%d", i.Id) return fmt.Sprintf("space_%d", i.Id)
} }
// SpacePath path of the blank page // path of the blank page
func (i *Image) SpacePath() string { func (i *Image) SpacePath() string {
return fmt.Sprintf("Text/%s.xhtml", i.SpaceKey()) return fmt.Sprintf("Text/%s.xhtml", i.SpaceKey())
} }
// EPUBSpacePath path of the blank page into the EPUB // path of the blank page into the EPUB
func (i *Image) EPUBSpacePath() string { func (i *Image) EPUBSpacePath() string {
return fmt.Sprintf("OEBPS/%s", i.SpacePath()) return fmt.Sprintf("OEBPS/%s", i.SpacePath())
} }
// PageKey key for page // key for page
func (i *Image) PageKey() string { func (i *Image) PageKey() string {
return fmt.Sprintf("page_%d_p%d", i.Id, i.Part) return fmt.Sprintf("page_%d_p%d", i.Id, i.Part)
} }
// PagePath page path linked to the image // page path linked to the image
func (i *Image) PagePath() string { func (i *Image) PagePath() string {
return fmt.Sprintf("Text/%s.xhtml", i.PageKey()) return fmt.Sprintf("Text/%s.xhtml", i.PageKey())
} }
// EPUBPagePath page path into the EPUB // page path into the EPUB
func (i *Image) EPUBPagePath() string { func (i *Image) EPUBPagePath() string {
return fmt.Sprintf("OEBPS/%s", i.PagePath()) return fmt.Sprintf("OEBPS/%s", i.PagePath())
} }
// ImgKey key for image // key for image
func (i *Image) ImgKey() string { func (i *Image) ImgKey() string {
return fmt.Sprintf("img_%d_p%d", i.Id, i.Part) return fmt.Sprintf("img_%d_p%d", i.Id, i.Part)
} }
// ImgPath image path // image path
func (i *Image) ImgPath() string { func (i *Image) ImgPath() string {
return fmt.Sprintf("Images/%s.%s", i.ImgKey(), i.Format) return fmt.Sprintf("Images/%s.%s", i.ImgKey(), i.Format)
} }
// EPUBImgPath image path into the EPUB // image path into the EPUB
func (i *Image) EPUBImgPath() string { func (i *Image) EPUBImgPath() string {
return fmt.Sprintf("OEBPS/%s", i.ImgPath()) return fmt.Sprintf("OEBPS/%s", i.ImgPath())
} }
// ImgStyle style to apply to the image. // style to apply to the image.
// //
// center by default. // center by default.
// align to left or right if it's part of the split 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, align string) string {
relWidth, relHeight := i.RelSize(viewWidth, viewHeight) relWidth, relHeight := i.RelSize(viewWidth, viewHeight)
marginW, marginH := float64(viewWidth-relWidth)/2, float64(viewHeight-relHeight)/2 marginW, marginH := float64(viewWidth-relWidth)/2, float64(viewHeight-relHeight)/2
var style []string style := []string{}
style = append(style, fmt.Sprintf("width:%dpx", relWidth)) style = append(style, fmt.Sprintf("width:%dpx", relWidth))
style = append(style, fmt.Sprintf("height:%dpx", relHeight)) style = append(style, fmt.Sprintf("height:%dpx", relHeight))

View File

@ -8,8 +8,8 @@ import (
"github.com/disintegration/gift" "github.com/disintegration/gift"
) )
// AutoContrast Automatically improve contrast // Automatically improve contrast
func AutoContrast() gift.Filter { func AutoContrast() *autocontrast {
return &autocontrast{} return &autocontrast{}
} }
@ -78,7 +78,7 @@ func (f *autocontrast) Draw(dst draw.Image, src image.Image, options *gift.Optio
// compute a curve from dark and light factor applying to the color // compute a curve from dark and light factor applying to the color
c := (1 - d) + (d+l)*y c := (1 - d) + (d+l)*y
// applying the ratio // applying the coef
return f.cap(r0 * c), f.cap(g0 * c), f.cap(b0 * c), a0 return f.cap(r0 * c), f.cap(g0 * c), f.cap(b0 * c), a0
}).Draw(dst, src, options) }).Draw(dst, src, options)
} }

View File

@ -7,7 +7,7 @@ import (
"github.com/disintegration/gift" "github.com/disintegration/gift"
) )
// AutoCrop Lookup for margin and crop // Lookup for margin and crop
func AutoCrop(img image.Image, bounds image.Rectangle, cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom int) gift.Filter { func AutoCrop(img image.Image, bounds image.Rectangle, cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom int) gift.Filter {
return gift.Crop( return gift.Crop(
findMargin(img, bounds, cutRatioOptions{cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom}), findMargin(img, bounds, cutRatioOptions{cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom}),

View File

@ -11,7 +11,7 @@ import (
"golang.org/x/image/font/gofont/gomonobold" "golang.org/x/image/font/gofont/gomonobold"
) )
// CoverTitle Create a title with the cover image // Create a title with the cover image
func CoverTitle(title string, align string, pctWidth int, pctMargin int, maxFontSize int, borderSize int) gift.Filter { 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}
} }
@ -25,13 +25,13 @@ type coverTitle struct {
borderSize int borderSize int
} }
// Bounds size is the same as source // 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 return srcBounds
} }
// Draw blur the src image, and create a box with the title in the middle // 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, options *gift.Options) {
draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src) draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
if p.title == "" { if p.title == "" {
return return

View File

@ -7,8 +7,7 @@ import (
"github.com/disintegration/gift" "github.com/disintegration/gift"
) )
// CropSplitDoublePage Cut a double page in 2 part: left and right. // Cut a double page in 2 part: left and right.
//
// This will cut in the middle of the page. // This will cut in the middle of the page.
func CropSplitDoublePage(right bool) gift.Filter { func CropSplitDoublePage(right bool) gift.Filter {
return &cropSplitDoublePage{right} return &cropSplitDoublePage{right}

View File

@ -8,8 +8,7 @@ import (
"github.com/disintegration/gift" "github.com/disintegration/gift"
) )
// Pixel Generate a blank pixel 1x1, if the size of the image is 0x0. // Generate a blank pixel 1x1, if the size of the image is 0x0.
//
// An image 0x0 is not a valid image, and failed to read. // An image 0x0 is not a valid image, and failed to read.
func Pixel() gift.Filter { func Pixel() gift.Filter {
return &pixel{} return &pixel{}
@ -27,7 +26,7 @@ func (p *pixel) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
return return
} }
func (p *pixel) Draw(dst draw.Image, src image.Image, _ *gift.Options) { func (p *pixel) Draw(dst draw.Image, src image.Image, options *gift.Options) {
if dst.Bounds().Dx() == 1 && dst.Bounds().Dy() == 1 { if dst.Bounds().Dx() == 1 && dst.Bounds().Dy() == 1 {
dst.Set(0, 0, color.White) dst.Set(0, 0, color.White)
return return

View File

@ -1,4 +1,6 @@
// Package epubimageprocessor extract and transform image into a compressed jpeg. /*
Extract and transform image into a compressed jpeg.
*/
package epubimageprocessor package epubimageprocessor
import ( import (
@ -25,7 +27,7 @@ func New(o *epuboptions.Options) *EPUBImageProcessor {
return &EPUBImageProcessor{o} return &EPUBImageProcessor{o}
} }
// Load extract and convert images // extract and convert images
func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
images = make([]*epubimage.Image, 0) images = make([]*epubimage.Image, 0)
imageCount, imageInput, err := e.load() imageCount, imageInput, err := e.load()
@ -33,7 +35,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
return nil, err return nil, err
} }
// dry run, skip conversion // dry run, skip convertion
if e.Dry { if e.Dry {
for img := range imageInput { for img := range imageInput {
images = append(images, &epubimage.Image{ images = append(images, &epubimage.Image{
@ -60,7 +62,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
}) })
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
imgStorage, err := epubzip.NewStorageImageWriter(e.ImgStorage(), e.Image.Format) imgStorage, err := epubzip.NewEPUBZipStorageImageWriter(e.ImgStorage(), e.Image.Format)
if err != nil { if err != nil {
bar.Close() bar.Close()
return nil, err return nil, err
@ -80,7 +82,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
// do not keep double page if requested // do not keep double page if requested
if !(img.DoublePage && input.Id > 0 && if !(img.DoublePage && input.Id > 0 &&
e.Options.Image.AutoSplitDoublePage && !e.Options.Image.KeepDoublePageIfSplit) { e.Options.Image.AutoSplitDoublePage && !e.Options.Image.KeepDoublePageIfSplitted) {
if err = imgStorage.Add(img.EPUBImgPath(), img.Raw, e.Image.Quality); err != nil { if err = imgStorage.Add(img.EPUBImgPath(), img.Raw, e.Image.Quality); err != nil {
bar.Close() bar.Close()
fmt.Fprintf(os.Stderr, "error with %s: %s", input.Name, err) fmt.Fprintf(os.Stderr, "error with %s: %s", input.Name, err)
@ -279,26 +281,26 @@ type CoverTitleDataOptions struct {
func (e *EPUBImageProcessor) Cover16LevelOfGray(bounds image.Rectangle) draw.Image { func (e *EPUBImageProcessor) Cover16LevelOfGray(bounds image.Rectangle) draw.Image {
return image.NewPaletted(bounds, color.Palette{ return image.NewPaletted(bounds, color.Palette{
color.Gray{}, color.Gray{0x00},
color.Gray{Y: 0x11}, color.Gray{0x11},
color.Gray{Y: 0x22}, color.Gray{0x22},
color.Gray{Y: 0x33}, color.Gray{0x33},
color.Gray{Y: 0x44}, color.Gray{0x44},
color.Gray{Y: 0x55}, color.Gray{0x55},
color.Gray{Y: 0x66}, color.Gray{0x66},
color.Gray{Y: 0x77}, color.Gray{0x77},
color.Gray{Y: 0x88}, color.Gray{0x88},
color.Gray{Y: 0x99}, color.Gray{0x99},
color.Gray{Y: 0xAA}, color.Gray{0xAA},
color.Gray{Y: 0xBB}, color.Gray{0xBB},
color.Gray{Y: 0xCC}, color.Gray{0xCC},
color.Gray{Y: 0xDD}, color.Gray{0xDD},
color.Gray{Y: 0xEE}, color.Gray{0xEE},
color.Gray{Y: 0xFF}, color.Gray{0xFF},
}) })
} }
// CoverTitleData create a title page with the cover // create a title page with the cover
func (e *EPUBImageProcessor) CoverTitleData(o *CoverTitleDataOptions) (*epubzip.ZipImage, error) { func (e *EPUBImageProcessor) CoverTitleData(o *CoverTitleDataOptions) (*epubzip.ZipImage, error) {
// Create a blur version of the cover // Create a blur version of the cover
g := gift.New(epubimagefilters.CoverTitle(o.Text, o.Align, o.PctWidth, o.PctMargin, o.MaxFontSize, o.BorderSize)) g := gift.New(epubimagefilters.CoverTitle(o.Text, o.Align, o.PctWidth, o.PctMargin, o.MaxFontSize, o.BorderSize))

View File

@ -208,7 +208,7 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err
return return
} }
var names []string names := []string{}
for _, img := range images { for _, img := range images {
names = append(names, img.Name) names = append(names, img.Name)
} }

View File

@ -1,4 +1,6 @@
// Package epuboptions Options for EPUB creation. /*
Options for EPUB creation.
*/
package epuboptions package epuboptions
import "fmt" import "fmt"
@ -20,23 +22,23 @@ type View struct {
} }
type Image struct { type Image struct {
Crop *Crop Crop *Crop
Quality int Quality int
Brightness int Brightness int
Contrast int Contrast int
AutoContrast bool AutoContrast bool
AutoRotate bool AutoRotate bool
AutoSplitDoublePage bool AutoSplitDoublePage bool
KeepDoublePageIfSplit bool KeepDoublePageIfSplitted bool
NoBlankImage bool NoBlankImage bool
Manga bool Manga bool
HasCover bool HasCover bool
View *View View *View
GrayScale bool GrayScale bool
GrayScaleMode int GrayScaleMode int
Resize bool Resize bool
Format string Format string
AppleBookCompatibility bool AppleBookCompatibility bool
} }
type Options struct { type Options struct {

View File

@ -1,4 +1,6 @@
// Package epubprogress create a progress bar with custom settings. /*
create a progress bar with custom settings.
*/
package epubprogress package epubprogress
import ( import (

View File

@ -5,20 +5,20 @@ import (
"os" "os"
) )
type Json struct { type EpubProgressJson struct {
o Options o Options
e *json.Encoder e *json.Encoder
current int current int
} }
func newEpubProgressJson(o Options) EpubProgress { func newEpubProgressJson(o Options) EpubProgress {
return &Json{ return &EpubProgressJson{
o: o, o: o,
e: json.NewEncoder(os.Stdout), e: json.NewEncoder(os.Stdout),
} }
} }
func (p *Json) Add(num int) error { func (p *EpubProgressJson) Add(num int) error {
p.current += num p.current += num
p.e.Encode(map[string]any{ p.e.Encode(map[string]any{
"type": "progress", "type": "progress",
@ -37,6 +37,6 @@ func (p *Json) Add(num int) error {
return nil return nil
} }
func (p *Json) Close() error { func (p *EpubProgressJson) Close() error {
return nil return nil
} }

View File

@ -1,4 +1,6 @@
// Package epubtemplates Templates use to create xml files of the EPUB. /*
Templates use to create xml files of the EPUB.
*/
package epubtemplates package epubtemplates
import _ "embed" import _ "embed"

View File

@ -30,9 +30,7 @@ type tag struct {
value string value string
} }
// Content create the content file // create the content file
//
//goland:noinspection HttpUrlsUsage,HttpUrlsUsage,HttpUrlsUsage,HttpUrlsUsage
func Content(o *ContentOptions) string { func Content(o *ContentOptions) string {
doc := etree.NewDocument() doc := etree.NewDocument()
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`) doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
@ -180,7 +178,7 @@ func getManifest(o *ContentOptions) []tag {
img, img,
!o.ImageOptions.View.PortraitOnly && !o.ImageOptions.View.PortraitOnly &&
(img.DoublePage || (img.DoublePage ||
(!o.ImageOptions.KeepDoublePageIfSplit && img.Part == 1) || (!o.ImageOptions.KeepDoublePageIfSplitted && img.Part == 1) ||
(img.Part == 0 && img == lastImage))) (img.Part == 0 && img == lastImage)))
} }
@ -214,7 +212,7 @@ func getSpineAuto(o *ContentOptions) []tag {
return fmt.Sprintf("%s layout-blank", getSpread(false)) return fmt.Sprintf("%s layout-blank", getSpread(false))
} }
var spine []tag spine := []tag{}
if o.HasTitlePage { if o.HasTitlePage {
if !o.ImageOptions.AppleBookCompatibility { if !o.ImageOptions.AppleBookCompatibility {
spine = append(spine, spine = append(spine,
@ -255,7 +253,7 @@ func getSpineAuto(o *ContentOptions) []tag {
} }
func getSpinePortrait(o *ContentOptions) []tag { func getSpinePortrait(o *ContentOptions) []tag {
var spine []tag spine := []tag{}
if o.HasTitlePage { if o.HasTitlePage {
spine = append(spine, spine = append(spine,
tag{"itemref", tagAttrs{"idref": "page_title"}, ""}, tag{"itemref", tagAttrs{"idref": "page_title"}, ""},
@ -271,7 +269,7 @@ func getSpinePortrait(o *ContentOptions) []tag {
return spine return spine
} }
// getGuide Section guide of the content // guide part of the content
func getGuide(o *ContentOptions) []tag { func getGuide(o *ContentOptions) []tag {
return []tag{ return []tag{
{"reference", tagAttrs{"type": "cover", "title": "cover", "href": "Text/cover.xhtml"}, ""}, {"reference", tagAttrs{"type": "cover", "title": "cover", "href": "Text/cover.xhtml"}, ""},

View File

@ -8,9 +8,7 @@ import (
epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image" epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
) )
// Toc create 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.Image) string {
doc := etree.NewDocument() doc := etree.NewDocument()
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`) doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)

View File

@ -1,5 +1,5 @@
/* /*
Package epubtree Organize a list of filename with their path into a tree of directories. Organize a list of filename with their path into a tree of directories.
Example: Example:
- A/B/C/D.jpg - A/B/C/D.jpg
@ -23,60 +23,52 @@ import (
"strings" "strings"
) )
type Tree struct { type tree struct {
nodes map[string]*Node Nodes map[string]*node
} }
type Node struct { type node struct {
value string Value string
children []*Node Children []*node
} }
// New initialize tree with a root node // initilize tree with a root node
func New() *Tree { func New() *tree {
return &Tree{map[string]*Node{ return &tree{map[string]*node{
".": {".", []*Node{}}, ".": {".", []*node{}},
}} }}
} }
// Root root node // root node
func (n *Tree) Root() *Node { func (n *tree) Root() *node {
return n.nodes["."] return n.Nodes["."]
} }
// Add the filename to the tree // add the filename to the tree
func (n *Tree) Add(filename string) { func (n *tree) Add(filename string) {
cn := n.Root() cn := n.Root()
cp := "" cp := ""
for _, p := range strings.Split(filepath.Clean(filename), string(filepath.Separator)) { for _, p := range strings.Split(filepath.Clean(filename), string(filepath.Separator)) {
cp = filepath.Join(cp, p) cp = filepath.Join(cp, p)
if _, ok := n.nodes[cp]; !ok { if _, ok := n.Nodes[cp]; !ok {
n.nodes[cp] = &Node{value: p, children: []*Node{}} n.Nodes[cp] = &node{Value: p, Children: []*node{}}
cn.children = append(cn.children, n.nodes[cp]) cn.Children = append(cn.Children, n.Nodes[cp])
} }
cn = n.nodes[cp] cn = n.Nodes[cp]
} }
} }
func (n *Node) ChildCount() int { // string version of the tree
return len(n.children) func (n *node) WriteString(indent string) string {
}
func (n *Node) FirstChild() *Node {
return n.children[0]
}
// WriteString string version of the tree
func (n *Node) WriteString(indent string) string {
r := strings.Builder{} r := strings.Builder{}
if indent != "" { if indent != "" {
r.WriteString(indent) r.WriteString(indent)
r.WriteString("- ") r.WriteString("- ")
r.WriteString(n.value) r.WriteString(n.Value)
r.WriteString("\n") r.WriteString("\n")
} }
indent += " " indent += " "
for _, c := range n.children { for _, c := range n.Children {
r.WriteString(c.WriteString(indent)) r.WriteString(c.WriteString(indent))
} }
return r.String() return r.String()

View File

@ -1,5 +1,5 @@
/* /*
Package epubzip Helper to write EPUB files. Helper to write EPUB files.
We create a zip with the magic EPUB mimetype. We create a zip with the magic EPUB mimetype.
*/ */
@ -16,7 +16,7 @@ type EPUBZip struct {
wz *zip.Writer wz *zip.Writer
} }
// New create a new EPUB // create a new EPUB
func New(path string) (*EPUBZip, error) { func New(path string) (*EPUBZip, error) {
w, err := os.Create(path) w, err := os.Create(path)
if err != nil { if err != nil {
@ -26,7 +26,7 @@ func New(path string) (*EPUBZip, error) {
return &EPUBZip{w, wz}, nil return &EPUBZip{w, wz}, nil
} }
// Close compress pipe and file. // close compress pipe and file.
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
@ -34,11 +34,10 @@ func (e *EPUBZip) Close() error {
return e.w.Close() return e.w.Close()
} }
// WriteMagic Write mimetype, in a very specific way. // Write mimetype, in a very specific way.
//
// This will be valid with epubcheck tools. // This will be valid with epubcheck tools.
func (e *EPUBZip) WriteMagic() error { func (e *EPUBZip) WriteMagic() error {
t := time.Now().UTC() t := time.Now()
fh := &zip.FileHeader{ fh := &zip.FileHeader{
Name: "mimetype", Name: "mimetype",
Method: zip.Store, Method: zip.Store,
@ -49,8 +48,6 @@ func (e *EPUBZip) WriteMagic() error {
UncompressedSize64: 20, UncompressedSize64: 20,
CRC32: 0x2cab616f, CRC32: 0x2cab616f,
} }
fh.CreatorVersion = fh.CreatorVersion&0xff00 | 20 // preserve compatibility byte
fh.ReaderVersion = 20
fh.SetMode(0600) fh.SetMode(0600)
m, err := e.wz.CreateRaw(fh) m, err := e.wz.CreateRaw(fh)
@ -65,7 +62,7 @@ func (e *EPUBZip) Copy(fz *zip.File) error {
return e.wz.Copy(fz) return e.wz.Copy(fz)
} }
// WriteRaw Write image. They are already compressed, so we write them down directly. // Write image. They are already compressed, so we write them down directly.
func (e *EPUBZip) WriteRaw(raw *ZipImage) error { func (e *EPUBZip) WriteRaw(raw *ZipImage) error {
m, err := e.wz.CreateRaw(raw.Header) m, err := e.wz.CreateRaw(raw.Header)
if err != nil { if err != nil {
@ -75,7 +72,7 @@ func (e *EPUBZip) WriteRaw(raw *ZipImage) error {
return err return err
} }
// WriteContent Write file. Compressed it using deflate. // 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{ m, err := e.wz.CreateHeader(&zip.FileHeader{
Name: file, Name: file,

View File

@ -17,7 +17,7 @@ type ZipImage struct {
Data []byte Data []byte
} }
// CompressImage create gzip encoded jpeg // 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) (*ZipImage, error) {
var ( var (
data, cdata bytes.Buffer data, cdata bytes.Buffer

View File

@ -7,23 +7,23 @@ import (
"sync" "sync"
) )
type StorageImageWriter struct { type EPUBZipStorageImageWriter struct {
fh *os.File fh *os.File
fz *zip.Writer fz *zip.Writer
format string format string
mut *sync.Mutex mut *sync.Mutex
} }
func NewStorageImageWriter(filename string, format string) (*StorageImageWriter, error) { func NewEPUBZipStorageImageWriter(filename string, format string) (*EPUBZipStorageImageWriter, error) {
fh, err := os.Create(filename) fh, err := os.Create(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
fz := zip.NewWriter(fh) fz := zip.NewWriter(fh)
return &StorageImageWriter{fh, fz, format, &sync.Mutex{}}, nil return &EPUBZipStorageImageWriter{fh, fz, format, &sync.Mutex{}}, nil
} }
func (e *StorageImageWriter) Close() error { func (e *EPUBZipStorageImageWriter) Close() error {
if err := e.fz.Close(); err != nil { if err := e.fz.Close(); err != nil {
e.fh.Close() e.fh.Close()
return err return err
@ -31,7 +31,7 @@ func (e *StorageImageWriter) Close() error {
return e.fh.Close() return e.fh.Close()
} }
func (e *StorageImageWriter) Add(filename string, img image.Image, quality int) error { func (e *EPUBZipStorageImageWriter) Add(filename string, img image.Image, quality int) error {
zipImage, err := CompressImage(filename, e.format, img, quality) zipImage, err := CompressImage(filename, e.format, img, quality)
if err != nil { if err != nil {
return err return err
@ -51,7 +51,7 @@ func (e *StorageImageWriter) Add(filename string, img image.Image, quality int)
return nil return nil
} }
type StorageImageReader struct { type EPUBZipStorageImageReader struct {
filename string filename string
fh *os.File fh *os.File
fz *zip.Reader fz *zip.Reader
@ -59,7 +59,7 @@ type StorageImageReader struct {
files map[string]*zip.File files map[string]*zip.File
} }
func NewStorageImageReader(filename string) (*StorageImageReader, error) { func NewEPUBZipStorageImageReader(filename string) (*EPUBZipStorageImageReader, error) {
fh, err := os.Open(filename) fh, err := os.Open(filename)
if err != nil { if err != nil {
return nil, err return nil, err
@ -76,14 +76,14 @@ func NewStorageImageReader(filename string) (*StorageImageReader, error) {
for _, z := range fz.File { for _, z := range fz.File {
files[z.Name] = z files[z.Name] = z
} }
return &StorageImageReader{filename, fh, fz, files}, nil return &EPUBZipStorageImageReader{filename, fh, fz, files}, nil
} }
func (e *StorageImageReader) Get(filename string) *zip.File { func (e *EPUBZipStorageImageReader) Get(filename string) *zip.File {
return e.files[filename] return e.files[filename]
} }
func (e *StorageImageReader) Size(filename string) uint64 { func (e *EPUBZipStorageImageReader) Size(filename string) uint64 {
img := e.Get(filename) img := e.Get(filename)
if img != nil { if img != nil {
return img.CompressedSize64 + 30 + uint64(len(img.Name)) return img.CompressedSize64 + 30 + uint64(len(img.Name))
@ -91,10 +91,10 @@ func (e *StorageImageReader) Size(filename string) uint64 {
return 0 return 0
} }
func (e *StorageImageReader) Close() error { func (e *EPUBZipStorageImageReader) Close() error {
return e.fh.Close() return e.fh.Close()
} }
func (e *StorageImageReader) Remove() error { func (e *EPUBZipStorageImageReader) Remove() error {
return os.Remove(e.filename) return os.Remove(e.filename)
} }

View File

@ -1,7 +1,7 @@
/* /*
Package sortpath support sorting of path that may include number. sortpath support sorting of path that may include number.
A series of path can look like: A series of path can looks like:
- Tome1/Chap1/Image1.jpg - Tome1/Chap1/Image1.jpg
- Tome1/Chap2/Image1.jpg - Tome1/Chap2/Image1.jpg
- Tome1/Chap10/Image2.jpg - Tome1/Chap10/Image2.jpg
@ -11,8 +11,8 @@ and compare them by decomposing the string and number part.
The module support 3 mode: The module support 3 mode:
- mode=0 alpha for path and file - mode=0 alpha for path and file
- mode=1 alphanumeric for path and alpha for file - mode=1 alphanum for path and alpha for file
- mode=2 alphanumeric for path and file - mode=2 alphanum for path and file
Example: Example:
@ -26,8 +26,6 @@ Example:
*/ */
package sortpath package sortpath
import "sort"
// struct that implement interface for sort.Sort // struct that implement interface for sort.Sort
type by struct { type by struct {
filenames []string filenames []string
@ -41,9 +39,9 @@ func (b by) Swap(i, j int) {
b.paths[i], b.paths[j] = b.paths[j], b.paths[i] b.paths[i], b.paths[j] = b.paths[j], b.paths[i]
} }
// By use sortpath.By with sort.Sort // use sortpath.By with sort.Sort
func By(filenames []string, mode int) sort.Interface { func By(filenames []string, mode int) by {
var p [][]part p := [][]part{}
for _, filename := range filenames { for _, filename := range filenames {
p = append(p, parse(filename, mode)) p = append(p, parse(filename, mode))
} }

View File

@ -8,7 +8,7 @@ import (
) )
// Strings follow with numbers like: s1, s1.2, s2-3, ... // Strings follow with numbers like: s1, s1.2, s2-3, ...
var splitPathRegex = regexp.MustCompile(`^(.*?)(\d+(?:\.\d+)?)(?:-(\d+(?:\.\d+)?))?$`) var split_path_regex = regexp.MustCompile(`^(.*?)(\d+(?:\.\d+)?)(?:-(\d+(?:\.\d+)?))?$`)
type part struct { type part struct {
fullname string fullname string
@ -31,7 +31,7 @@ func (a part) compare(b part) float64 {
// separate from the string the number part. // separate from the string the number part.
func parsePart(p string) part { func parsePart(p string) part {
r := splitPathRegex.FindStringSubmatch(p) r := split_path_regex.FindStringSubmatch(p)
if len(r) == 0 { if len(r) == 0 {
return part{p, p, 0} return part{p, p, 0}
} }
@ -43,23 +43,23 @@ func parsePart(p string) part {
} }
// mode=0 alpha for path and file // mode=0 alpha for path and file
// mode=1 alphanumeric for path and alpha for file // mode=1 alphanum for path and alpha for file
// mode=2 alphanumeric for path and file // mode=2 alphanum for path and file
func parse(filename string, mode int) []part { func parse(filename string, mode int) []part {
pathname, name := filepath.Split(strings.ToLower(filename)) pathname, name := filepath.Split(strings.ToLower(filename))
pathname = strings.TrimSuffix(pathname, string(filepath.Separator)) pathname = strings.TrimSuffix(pathname, string(filepath.Separator))
ext := filepath.Ext(name) ext := filepath.Ext(name)
name = name[0 : len(name)-len(ext)] name = name[0 : len(name)-len(ext)]
var f []part f := []part{}
for _, p := range strings.Split(pathname, string(filepath.Separator)) { for _, p := range strings.Split(pathname, string(filepath.Separator)) {
if mode > 0 { // alphanumeric for path if mode > 0 { // alphanum for path
f = append(f, parsePart(p)) f = append(f, parsePart(p))
} else { } else {
f = append(f, part{p, p, 0}) f = append(f, part{p, p, 0})
} }
} }
if mode == 2 { // alphanumeric for file if mode == 2 { // alphanum for file
f = append(f, parsePart(name)) f = append(f, parsePart(name))
} else { } else {
f = append(f, part{name, name, 0}) f = append(f, part{name, name, 0})
@ -67,7 +67,7 @@ func parse(filename string, mode int) []part {
return f return f
} }
// compare 2 full path split into parts // compare 2 fullpath splitted into parts
func compareParts(a, b []part) float64 { func compareParts(a, b []part) float64 {
m := len(a) m := len(a)
if m > len(b) { if m > len(b) {

44
main.go
View File

@ -1,7 +1,7 @@
/* /*
Convert CBZ/CBR/Dir into EPUB for e-reader devices (Kindle Devices, ...) Convert CBZ/CBR/Dir into EPUB for e-reader devices (Kindle Devices, ...)
My goal is to make a simple, cross-platform, and fast tool to convert comics into EPUB. My goal is to make a simple, crossplatform, and fast tool to convert comics into EPUB.
EPUB is now support by Amazon through [SendToKindle](https://www.amazon.com/gp/sendtokindle/), by Email or by using the App. So I've made it simple to support the size limit constraint of those services. EPUB is now support by Amazon through [SendToKindle](https://www.amazon.com/gp/sendtokindle/), by Email or by using the App. So I've made it simple to support the size limit constraint of those services.
*/ */
@ -43,7 +43,7 @@ func main() {
fmt.Fprintln(os.Stderr, "failed to fetch the latest version") fmt.Fprintln(os.Stderr, "failed to fetch the latest version")
os.Exit(1) os.Exit(1)
} }
latestVersion := v.Versions[0] latest_version := v.Versions[0]
fmt.Fprintf(os.Stderr, `go-comic-converter fmt.Fprintf(os.Stderr, `go-comic-converter
Path : %s Path : %s
@ -57,9 +57,9 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
bi.Main.Path, bi.Main.Path,
bi.Main.Sum, bi.Main.Sum,
bi.Main.Version, bi.Main.Version,
latestVersion.Original(), latest_version.Original(),
latestVersion.Segments()[0], latest_version.Segments()[0],
latestVersion.Original(), latest_version.Original(),
) )
return return
} }
@ -122,23 +122,23 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
Quiet: cmd.Options.Quiet, Quiet: cmd.Options.Quiet,
Json: cmd.Options.Json, Json: cmd.Options.Json,
Image: &epuboptions.Image{ Image: &epuboptions.Image{
Crop: &epuboptions.Crop{Enabled: cmd.Options.Crop, Left: cmd.Options.CropRatioLeft, Up: cmd.Options.CropRatioUp, Right: cmd.Options.CropRatioRight, Bottom: cmd.Options.CropRatioBottom}, Crop: &epuboptions.Crop{Enabled: cmd.Options.Crop, Left: cmd.Options.CropRatioLeft, Up: cmd.Options.CropRatioUp, Right: cmd.Options.CropRatioRight, Bottom: cmd.Options.CropRatioBottom},
Quality: cmd.Options.Quality, Quality: cmd.Options.Quality,
Brightness: cmd.Options.Brightness, Brightness: cmd.Options.Brightness,
Contrast: cmd.Options.Contrast, Contrast: cmd.Options.Contrast,
AutoContrast: cmd.Options.AutoContrast, AutoContrast: cmd.Options.AutoContrast,
AutoRotate: cmd.Options.AutoRotate, AutoRotate: cmd.Options.AutoRotate,
AutoSplitDoublePage: cmd.Options.AutoSplitDoublePage, AutoSplitDoublePage: cmd.Options.AutoSplitDoublePage,
KeepDoublePageIfSplit: cmd.Options.KeepDoublePageIfSplit, KeepDoublePageIfSplitted: cmd.Options.KeepDoublePageIfSplitted,
NoBlankImage: cmd.Options.NoBlankImage, NoBlankImage: cmd.Options.NoBlankImage,
Manga: cmd.Options.Manga, Manga: cmd.Options.Manga,
HasCover: cmd.Options.HasCover, HasCover: cmd.Options.HasCover,
View: &epuboptions.View{Width: profile.Width, Height: profile.Height, AspectRatio: cmd.Options.AspectRatio, PortraitOnly: cmd.Options.PortraitOnly, Color: epuboptions.Color{Foreground: cmd.Options.ForegroundColor, Background: cmd.Options.BackgroundColor}}, View: &epuboptions.View{Width: profile.Width, Height: profile.Height, AspectRatio: cmd.Options.AspectRatio, PortraitOnly: cmd.Options.PortraitOnly, Color: epuboptions.Color{Foreground: cmd.Options.ForegroundColor, Background: cmd.Options.BackgroundColor}},
GrayScale: cmd.Options.Grayscale, GrayScale: cmd.Options.Grayscale,
GrayScaleMode: cmd.Options.GrayscaleMode, GrayScaleMode: cmd.Options.GrayscaleMode,
Resize: !cmd.Options.NoResize, Resize: !cmd.Options.NoResize,
Format: cmd.Options.Format, Format: cmd.Options.Format,
AppleBookCompatibility: cmd.Options.AppleBookCompatibility, AppleBookCompatibility: cmd.Options.AppleBookCompatibility,
}, },
}).Write(); err != nil { }).Write(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) fmt.Fprintf(os.Stderr, "Error: %v\n", err)