improve dry and sort

dry verbose list files in sorted order
sort support 3 mode
	mode 0: path=alpha, file=alpha
	mode 1: path=alphanum, file=alpha
	mode 2: path=alphanum, file=alphanum
improve alphanum sort, supporting double page like "p51-52"
This commit is contained in:
Celogeek 2023-04-09 18:21:29 +02:00
parent 105c21f8d2
commit fbb48830d4
Signed by: celogeek
SSH Key Fingerprint: SHA256:njNJLzoLQdbV9PC6ehcruRb0QnEgxABoCYZ+0+aUIYc
8 changed files with 235 additions and 141 deletions

View File

@ -127,6 +127,7 @@ Options:
AddPanelView : false AddPanelView : false
LimitMb : 200 Mb LimitMb : 200 Mb
StripFirstDirectoryFromToc: true StripFirstDirectoryFromToc: true
SortPathMode : path=alphanum, file=alpha
TOC: TOC:
- mymanga - mymanga
@ -134,6 +135,58 @@ TOC:
- Chapter 2 - Chapter 2
``` ```
## Dry verbose
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.
```
$ go-comic-converter -input ~/Downloads/mymanga.cbr -profile KS -auto -manga -limitmb 200 -dry -dry-verbose -sort 2
Go Comic Converter
Options:
Input : ~/Downloads/mymanga.cbr
Output : ~/Downloads/mymanga.epub
Author : GO Comic Converter
Title : mymanga
Workers : 8
Profile : KS - Kindle Scribe - 1860x2480 - 16 levels of gray
Quality : 85
Crop : true
Brightness : 0
Contrast : 0
AutoRotate : true
AutoSplitDoublePage : true
NoBlankPage : false
Manga : true
HasCover : true
AddPanelView : false
LimitMb : 200 Mb
StripFirstDirectoryFromToc: true
SortPathMode : path=alphanum, file=alphanum
TOC:
- mymanga
- Chapter 1
- Chapter 2
- Chapter 3
Files:
- Chapter 1
- img1.jpg
- img2.jpg
- img10.jpg
- Chapter 2
- img01.jpg
- img02.jpg
- img03.jpg
- Chapter 3
- img1.jpg
- img2-3.jpg
- img4.jpg
```
## Change default settings ## Change default settings
### Show current default option ### Show current default option
@ -156,6 +209,7 @@ Options:
AddPanelView : false AddPanelView : false
LimitMb : nolimit LimitMb : nolimit
StripFirstDirectoryFromToc: false StripFirstDirectoryFromToc: false
SortPathMode : path=alphanum, file=alpha
``` ```
### Change default settings ### Change default settings
@ -178,6 +232,7 @@ Options:
AddPanelView : false AddPanelView : false
LimitMb : 200 Mb LimitMb : 200 Mb
StripFirstDirectoryFromToc: false StripFirstDirectoryFromToc: false
SortPathMode : path=alphanum, file=alpha
Saving to /Users/vincent/.go-comic-converter.yaml Saving to /Users/vincent/.go-comic-converter.yaml
``` ```
@ -202,43 +257,11 @@ Options:
AddPanelView : false AddPanelView : false
LimitMb : 200 Mb LimitMb : 200 Mb
StripFirstDirectoryFromToc: false StripFirstDirectoryFromToc: false
SortPathMode : path=alphanum, file=alpha
Saving to /Users/vincent/.go-comic-converter.yaml Saving to /Users/vincent/.go-comic-converter.yaml
``` ```
### Check
You can test the command dry above like
```
$ go-comic-converter -input ~/Downloads/mymanga.cbr -dry
Go Comic Converter
Options:
Input : ~/Downloads/mymanga.cbr
Output : ~/Downloads/mymanga.epub
Author : GO Comic Converter
Title : mymanga
Workers : 8
Profile : KS - Kindle Scribe - 1860x2480 - 16 levels of gray
Quality : 85
Crop : true
Brightness : 0
Contrast : 0
AutoRotate : true
AutoSplitDoublePage : true
NoBlankPage : false
Manga : false
HasCover : true
AddPanelView : false
LimitMb : 200 Mb
StripFirstDirectoryFromToc: false
TOC:
- mymanga
- Chapter 1
- Chapter 2
```
### Reset default ### Reset default
To reset all value to default: To reset all value to default:
@ -283,6 +306,8 @@ Output:
Number of workers Number of workers
-dry -dry
Dry run to show all options Dry run to show all options
-dry-verbose
Display also sorted files after the TOC
Config: Config:
-profile string -profile string
@ -337,6 +362,11 @@ Config:
Limit size of the ePub: Default nolimit (0), Minimum 20 Limit size of the ePub: Default nolimit (0), Minimum 20
-strip -strip
Strip first directory from the TOC if only 1 Strip first directory from the TOC if only 1
-sort int (default 1)
Sort path mode
0 = alpha for path and file
1 = alphanum for path and alpha for file
2 = alphanum for path and file
Default config: Default config:
-show -show

View File

@ -81,6 +81,7 @@ func (c *Converter) InitParse() {
c.AddStringParam(&c.Options.Title, "title", "", "Title of the epub") c.AddStringParam(&c.Options.Title, "title", "", "Title of the epub")
c.AddIntParam(&c.Options.Workers, "workers", runtime.NumCPU(), "Number of workers") c.AddIntParam(&c.Options.Workers, "workers", runtime.NumCPU(), "Number of workers")
c.AddBoolParam(&c.Options.Dry, "dry", false, "Dry run to show all options") c.AddBoolParam(&c.Options.Dry, "dry", false, "Dry run to show all options")
c.AddBoolParam(&c.Options.DryVerbose, "dry-verbose", false, "Display also sorted files after the TOC")
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()))
@ -97,6 +98,7 @@ func (c *Converter) InitParse() {
c.AddBoolParam(&c.Options.AddPanelView, "addpanelview", c.Options.AddPanelView, "Add an embeded panel view. On kindle you may not need this option as it is handled by the kindle.") c.AddBoolParam(&c.Options.AddPanelView, "addpanelview", c.Options.AddPanelView, "Add an embeded panel view. On kindle you may not need this option as it is handled by the kindle.")
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.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")
@ -260,6 +262,10 @@ func (c *Converter) Validate() error {
return errors.New("contrast should be between -100 and 100") return errors.New("contrast should be between -100 and 100")
} }
if c.Options.SortPathMode < 0 || c.Options.SortPathMode > 2 {
return errors.New("sort should be 0, 1 or 2")
}
return nil return nil
} }

View File

@ -11,13 +11,14 @@ import (
type Options struct { type Options struct {
// Output // Output
Input string `yaml:"-"` Input string `yaml:"-"`
Output string `yaml:"-"` Output string `yaml:"-"`
Author string `yaml:"-"` Author string `yaml:"-"`
Title string `yaml:"-"` Title string `yaml:"-"`
Auto bool `yaml:"-"` Auto bool `yaml:"-"`
Workers int `yaml:"-"` Workers int `yaml:"-"`
Dry bool `yaml:"-"` Dry bool `yaml:"-"`
DryVerbose bool `yaml:"-"`
// Config // Config
Profile string `yaml:"profile"` Profile string `yaml:"profile"`
@ -33,6 +34,7 @@ type Options struct {
AddPanelView bool `yaml:"add_panel_view"` AddPanelView bool `yaml:"add_panel_view"`
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"`
// Default Config // Default Config
Show bool `yaml:"-"` Show bool `yaml:"-"`
@ -62,6 +64,7 @@ func New() *Options {
AddPanelView: false, AddPanelView: false,
LimitMb: 0, LimitMb: 0,
StripFirstDirectoryFromToc: false, StripFirstDirectoryFromToc: false,
SortPathMode: 1,
profiles: profiles.New(), profiles: profiles.New(),
} }
} }
@ -127,6 +130,16 @@ func (o *Options) ShowDefault() string {
limitmb = fmt.Sprintf("%d Mb", o.LimitMb) limitmb = fmt.Sprintf("%d Mb", o.LimitMb)
} }
sortpathmode := ""
switch o.SortPathMode {
case 0:
sortpathmode = "path=alpha, file=alpha"
case 1:
sortpathmode = "path=alphanum, file=alpha"
case 2:
sortpathmode = "path=alphanum, file=alphanum"
}
return fmt.Sprintf(` return fmt.Sprintf(`
Profile : %s Profile : %s
Quality : %d Quality : %d
@ -140,7 +153,8 @@ func (o *Options) ShowDefault() string {
HasCover : %v HasCover : %v
AddPanelView : %v AddPanelView : %v
LimitMb : %s LimitMb : %s
StripFirstDirectoryFromToc: %v`, StripFirstDirectoryFromToc: %v
SortPathMode : %s`,
profileDesc, profileDesc,
o.Quality, o.Quality,
o.Crop, o.Crop,
@ -154,6 +168,7 @@ func (o *Options) ShowDefault() string {
o.AddPanelView, o.AddPanelView,
limitmb, limitmb,
o.StripFirstDirectoryFromToc, o.StripFirstDirectoryFromToc,
sortpathmode,
) )
} }

View File

@ -13,7 +13,6 @@ import (
"time" "time"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"gopkg.in/yaml.v3"
) )
type ImageOptions struct { type ImageOptions struct {
@ -42,6 +41,8 @@ type EpubOptions struct {
LimitMb int LimitMb int
StripFirstDirectoryFromToc bool StripFirstDirectoryFromToc bool
Dry bool Dry bool
DryVerbose bool
SortPathMode int
*ImageOptions *ImageOptions
} }
@ -97,7 +98,7 @@ func (e *ePub) render(templateString string, data any) string {
} }
func (e *ePub) getParts() ([]*epubPart, error) { func (e *ePub) getParts() ([]*epubPart, error) {
images, err := LoadImages(e.Input, e.ImageOptions, e.Dry) images, err := e.LoadImages()
if err != nil { if err != nil {
return nil, err return nil, err
@ -201,6 +202,32 @@ func (e *ePub) getToc(images []*Image) *TocChildren {
} }
func (e *ePub) getTree(images []*Image, skip_files bool) string {
r := []string{}
c := []string{}
for _, img := range images {
n := []string{}
if len(img.Path) > 0 {
n = strings.Split(filepath.Clean(img.Path), string(filepath.Separator))
}
for l, p := range n {
f := fmt.Sprintf("%%%ds- %%s", l*2)
if len(c) > l && c[l] == p {
continue
}
r = append(r, fmt.Sprintf(f, "", p))
}
c = n
if skip_files {
continue
}
f := fmt.Sprintf("%%%ds- %%s", len(n)*2+2)
r = append(r, fmt.Sprintf(f, "", img.Name))
}
return strings.Join(r, "\n")
}
func (e *ePub) Write() error { func (e *ePub) Write() error {
type zipContent struct { type zipContent struct {
Name string Name string
@ -213,10 +240,9 @@ func (e *ePub) Write() error {
} }
if e.Dry { if e.Dry {
tocChildren := e.getToc(epubParts[0].Images) fmt.Fprintf(os.Stderr, "TOC:\n- %s\n%s\n", e.Title, e.getTree(epubParts[0].Images, true))
fmt.Fprintf(os.Stderr, "TOC:\n- %s\n", e.Title) if e.DryVerbose {
if tocChildren != nil { fmt.Fprintf(os.Stderr, "\nFiles:\n%s\n", e.getTree(epubParts[0].Images, false))
yaml.NewEncoder(os.Stderr).Encode(tocChildren)
} }
return nil return nil
} }

View File

@ -34,13 +34,14 @@ type Image struct {
IsCover bool IsCover bool
NeedSpace bool NeedSpace bool
Path string Path string
Name string
} }
type imageTask struct { type imageTask struct {
Id int Id int
Reader io.ReadCloser Reader io.ReadCloser
Path string Path string
Filename string Name string
} }
func colorIsBlank(c color.Color) bool { func colorIsBlank(c color.Color) bool {
@ -94,10 +95,10 @@ BOTTOM:
return imgArea return imgArea
} }
func LoadImages(path string, options *ImageOptions, dry bool) ([]*Image, error) { func (e *ePub) LoadImages() ([]*Image, error) {
images := make([]*Image, 0) images := make([]*Image, 0)
fi, err := os.Stat(path) fi, err := os.Stat(e.Input)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -108,15 +109,15 @@ func LoadImages(path string, options *ImageOptions, dry bool) ([]*Image, error)
) )
if fi.IsDir() { if fi.IsDir() {
imageCount, imageInput, err = loadDir(path) imageCount, imageInput, err = loadDir(e.Input, e.SortPathMode)
} else { } else {
switch ext := strings.ToLower(filepath.Ext(path)); ext { switch ext := strings.ToLower(filepath.Ext(e.Input)); ext {
case ".cbz", ".zip": case ".cbz", ".zip":
imageCount, imageInput, err = loadCbz(path) imageCount, imageInput, err = loadCbz(e.Input, e.SortPathMode)
case ".cbr", ".rar": case ".cbr", ".rar":
imageCount, imageInput, err = loadCbr(path) imageCount, imageInput, err = loadCbr(e.Input, e.SortPathMode)
case ".pdf": case ".pdf":
imageCount, imageInput, err = loadPdf(path) imageCount, imageInput, err = loadPdf(e.Input)
default: default:
err = fmt.Errorf("unknown file format (%s): support .cbz, .zip, .cbr, .rar, .pdf", ext) err = fmt.Errorf("unknown file format (%s): support .cbz, .zip, .cbr, .rar, .pdf", ext)
} }
@ -125,17 +126,19 @@ func LoadImages(path string, options *ImageOptions, dry bool) ([]*Image, error)
return nil, err return nil, err
} }
if dry { if e.Dry {
for img := range imageInput { for img := range imageInput {
img.Reader.Close()
images = append(images, &Image{ images = append(images, &Image{
img.Id, Id: img.Id,
0, Part: 0,
nil, Data: nil,
0, Width: 0,
0, Height: 0,
false, IsCover: false,
false, // NeedSpace reajust during parts computation NeedSpace: false, // NeedSpace reajust during parts computation
img.Path, Path: img.Path,
Name: img.Name,
}) })
} }
@ -148,7 +151,7 @@ func LoadImages(path string, options *ImageOptions, dry bool) ([]*Image, error)
bar := NewBar(imageCount, "Processing", 1, 2) bar := NewBar(imageCount, "Processing", 1, 2)
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
for i := 0; i < options.Workers; i++ { for i := 0; i < e.ImageOptions.Workers; i++ {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
@ -156,58 +159,61 @@ func LoadImages(path string, options *ImageOptions, dry bool) ([]*Image, error)
for img := range imageInput { for img := range imageInput {
// Decode image // Decode image
src, _, err := image.Decode(img.Reader) src, _, err := image.Decode(img.Reader)
img.Reader.Close()
if err != nil { if err != nil {
bar.Clear() bar.Clear()
fmt.Fprintf(os.Stderr, "error processing image %s: %s\n", img.Filename, err) fmt.Fprintf(os.Stderr, "error processing image %s%s: %s\n", img.Path, img.Name, err)
os.Exit(1) os.Exit(1)
} }
if options.Crop { if e.ImageOptions.Crop {
g := gift.New(gift.Crop(findMarging(src))) g := gift.New(gift.Crop(findMarging(src)))
newSrc := image.NewNRGBA(g.Bounds(src.Bounds())) newSrc := image.NewNRGBA(g.Bounds(src.Bounds()))
g.Draw(newSrc, src) g.Draw(newSrc, src)
src = newSrc src = newSrc
} }
g := NewGift(options) g := NewGift(e.ImageOptions)
// Convert image // Convert image
dst := image.NewPaletted(g.Bounds(src.Bounds()), options.Palette) dst := image.NewPaletted(g.Bounds(src.Bounds()), e.ImageOptions.Palette)
g.Draw(dst, src) g.Draw(dst, src)
imageOutput <- &Image{ imageOutput <- &Image{
img.Id, Id: img.Id,
0, Part: 0,
newImageData(img.Id, 0, dst, options.Quality), Data: newImageData(img.Id, 0, dst, e.ImageOptions.Quality),
dst.Bounds().Dx(), Width: dst.Bounds().Dx(),
dst.Bounds().Dy(), Height: dst.Bounds().Dy(),
img.Id == 0, IsCover: img.Id == 0,
false, NeedSpace: false,
img.Path, Path: img.Path,
Name: img.Name,
} }
// Auto split double page // Auto split double page
// Except for cover // Except for cover
// Only if the src image have width > height and is bigger than the view // Only if the src image have width > height and is bigger than the view
if (!options.HasCover || img.Id > 0) && if (!e.ImageOptions.HasCover || img.Id > 0) &&
options.AutoSplitDoublePage && e.ImageOptions.AutoSplitDoublePage &&
src.Bounds().Dx() > src.Bounds().Dy() && src.Bounds().Dx() > src.Bounds().Dy() &&
src.Bounds().Dx() > options.ViewHeight && src.Bounds().Dx() > e.ImageOptions.ViewHeight &&
src.Bounds().Dy() > options.ViewWidth { src.Bounds().Dy() > e.ImageOptions.ViewWidth {
gifts := NewGiftSplitDoublePage(options) gifts := NewGiftSplitDoublePage(e.ImageOptions)
for i, g := range gifts { for i, g := range gifts {
part := i + 1 part := i + 1
dst := image.NewPaletted(g.Bounds(src.Bounds()), options.Palette) dst := image.NewPaletted(g.Bounds(src.Bounds()), e.ImageOptions.Palette)
g.Draw(dst, src) g.Draw(dst, src)
imageOutput <- &Image{ imageOutput <- &Image{
img.Id, Id: img.Id,
part, Part: part,
newImageData(img.Id, part, dst, options.Quality), Data: newImageData(img.Id, part, dst, e.ImageOptions.Quality),
dst.Bounds().Dx(), Width: dst.Bounds().Dx(),
dst.Bounds().Dy(), Height: dst.Bounds().Dy(),
false, IsCover: false,
false, // NeedSpace reajust during parts computation NeedSpace: false, // NeedSpace reajust during parts computation
img.Path, Path: img.Path,
Name: img.Name,
} }
} }
} }
@ -221,7 +227,7 @@ func LoadImages(path string, options *ImageOptions, dry bool) ([]*Image, error)
}() }()
for image := range imageOutput { for image := range imageOutput {
if !(options.NoBlankPage && image.Width == 1 && image.Height == 1) { if !(e.ImageOptions.NoBlankPage && image.Width == 1 && image.Height == 1) {
images = append(images, image) images = append(images, image)
} }
if image.Part == 0 { if image.Part == 0 {
@ -247,7 +253,7 @@ func isSupportedImage(path string) bool {
return false return false
} }
func loadDir(input string) (int, chan *imageTask, error) { func loadDir(input string, sortpathmode int) (int, chan *imageTask, error) {
images := make([]string, 0) images := make([]string, 0)
input = filepath.Clean(input) input = filepath.Clean(input)
err := filepath.WalkDir(input, func(path string, d fs.DirEntry, err error) error { err := filepath.WalkDir(input, func(path string, d fs.DirEntry, err error) error {
@ -267,7 +273,7 @@ func loadDir(input string) (int, chan *imageTask, error) {
return 0, nil, fmt.Errorf("image not found") return 0, nil, fmt.Errorf("image not found")
} }
sort.Sort(sortpath.By(images)) sort.Sort(sortpath.By(images, sortpathmode))
output := make(chan *imageTask) output := make(chan *imageTask)
go func() { go func() {
@ -278,24 +284,25 @@ func loadDir(input string) (int, chan *imageTask, error) {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
} }
p := filepath.Dir(img)
p, fn := filepath.Split(img)
if p == input { if p == input {
p = "" p = ""
} else { } else {
p = p[len(input)+1:] p = p[len(input)+1:]
} }
output <- &imageTask{ output <- &imageTask{
Id: i, Id: i,
Reader: f, Reader: f,
Path: p, Path: p,
Filename: img, Name: fn,
} }
} }
}() }()
return len(images), output, nil return len(images), output, nil
} }
func loadCbz(input string) (int, chan *imageTask, error) { func loadCbz(input string, sortpathmode int) (int, chan *imageTask, error) {
r, err := zip.OpenReader(input) r, err := zip.OpenReader(input)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
@ -316,7 +323,7 @@ func loadCbz(input string) (int, chan *imageTask, error) {
for _, img := range images { for _, img := range images {
names = append(names, img.Name) names = append(names, img.Name)
} }
sort.Sort(sortpath.By(names)) sort.Sort(sortpath.By(names, sortpathmode))
indexedNames := make(map[string]int) indexedNames := make(map[string]int)
for i, name := range names { for i, name := range names {
@ -332,18 +339,19 @@ func loadCbz(input string) (int, chan *imageTask, error) {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
} }
p, fn := filepath.Split(filepath.Clean(img.Name))
output <- &imageTask{ output <- &imageTask{
Id: indexedNames[img.Name], Id: indexedNames[img.Name],
Reader: f, Reader: f,
Path: filepath.Dir(filepath.Clean(img.Name)), Path: p,
Filename: img.Name, Name: fn,
} }
} }
}() }()
return len(images), output, nil return len(images), output, nil
} }
func loadCbr(input string) (int, chan *imageTask, error) { func loadCbr(input string, sortpathmode int) (int, chan *imageTask, error) {
// listing and indexing // listing and indexing
rl, err := rardecode.OpenReader(input, "") rl, err := rardecode.OpenReader(input, "")
if err != nil { if err != nil {
@ -372,7 +380,7 @@ func loadCbr(input string) (int, chan *imageTask, error) {
return 0, nil, fmt.Errorf("no images found") return 0, nil, fmt.Errorf("no images found")
} }
sort.Sort(sortpath.By(names)) sort.Sort(sortpath.By(names, sortpathmode))
indexedNames := make(map[string]int) indexedNames := make(map[string]int)
for i, name := range names { for i, name := range names {
@ -401,11 +409,13 @@ func loadCbr(input string) (int, chan *imageTask, error) {
b := bytes.NewBuffer([]byte{}) b := bytes.NewBuffer([]byte{})
io.Copy(b, r) io.Copy(b, r)
p, fn := filepath.Split(filepath.Clean(f.Name))
output <- &imageTask{ output <- &imageTask{
Id: idx, Id: idx,
Reader: io.NopCloser(b), Reader: io.NopCloser(b),
Path: filepath.Dir(filepath.Clean(f.Name)), Path: p,
Filename: f.Name, Name: fn,
} }
} }
} }
@ -421,6 +431,7 @@ func loadPdf(input string) (int, chan *imageTask, error) {
} }
nbPages := len(pdf.Pages()) nbPages := len(pdf.Pages())
pageFmt := fmt.Sprintf("page %%0%dd", len(fmt.Sprintf("%d", nbPages)))
output := make(chan *imageTask) output := make(chan *imageTask)
go func() { go func() {
defer close(output) defer close(output)
@ -438,10 +449,10 @@ func loadPdf(input string) (int, chan *imageTask, error) {
} }
output <- &imageTask{ output <- &imageTask{
Id: i, Id: i,
Reader: io.NopCloser(b), Reader: io.NopCloser(b),
Path: "/", Path: "",
Filename: fmt.Sprintf("page %d", i+1), Name: fmt.Sprintf(pageFmt, i+1),
} }
} }
}() }()

View File

@ -7,14 +7,19 @@ import (
"strings" "strings"
) )
var split_path_regex = regexp.MustCompile(`^(.*?)(\d+(?:\.\d+)?)$`) // Strings follow with numbers like: s1, s1.2, s2-3, ...
var split_path_regex = regexp.MustCompile(`^(.*?)(\d+(?:\.\d+)?)(?:-(\d+(?:\.\d+)?))?$`)
type part struct { type part struct {
name string fullname string
number float64 name string
number float64
} }
func (a part) Compare(b part) float64 { func (a part) Compare(b part) float64 {
if a.number == 0 || b.number == 0 {
return float64(strings.Compare(a.fullname, b.fullname))
}
if a.name == b.name { if a.name == b.name {
return a.number - b.number return a.number - b.number
} else { } else {
@ -25,16 +30,19 @@ func (a part) Compare(b part) float64 {
func parsePart(p string) part { func parsePart(p string) part {
r := split_path_regex.FindStringSubmatch(p) r := split_path_regex.FindStringSubmatch(p)
if len(r) == 0 { if len(r) == 0 {
return part{p, 0} return part{p, p, 0}
} }
n, err := strconv.ParseFloat(r[2], 64) n, err := strconv.ParseFloat(r[2], 64)
if err != nil { if err != nil {
return part{p, 0} return part{p, p, 0}
} }
return part{r[1], n} return part{p, r[1], n}
} }
func parse(filename string) []part { // mode=0 alpha for path and file
// mode=1 alphanum for path and alpha for file
// mode=2 alphanum for path and file
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)
@ -42,9 +50,17 @@ func parse(filename string) []part {
f := []part{} f := []part{}
for _, p := range strings.Split(pathname, string(filepath.Separator)) { for _, p := range strings.Split(pathname, string(filepath.Separator)) {
f = append(f, parsePart(p)) if mode > 0 { // alphanum for path
f = append(f, parsePart(p))
} else {
f = append(f, part{p, p, 0})
}
}
if mode == 2 { // alphanum for file
f = append(f, parsePart(name))
} else {
f = append(f, part{name, name, 0})
} }
f = append(f, parsePart(name))
return f return f
} }
@ -74,10 +90,10 @@ 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]
} }
func By(filenames []string) by { func By(filenames []string, mode int) by {
p := [][]part{} p := [][]part{}
for _, filename := range filenames { for _, filename := range filenames {
p = append(p, parse(filename)) p = append(p, parse(filename, mode))
} }
return by{filenames, p} return by{filenames, p}
} }

View File

@ -15,20 +15,8 @@ type TocChildren struct {
Tags []*TocPart Tags []*TocPart
} }
func (t *TocChildren) MarshalYAML() (any, error) {
return t.Tags, nil
}
type TocPart struct { type TocPart struct {
XMLName xml.Name `xml:"li"` XMLName xml.Name `xml:"li"`
Title TocTitle Title TocTitle
Children *TocChildren `xml:",omitempty"` Children *TocChildren `xml:",omitempty"`
} }
func (t *TocPart) MarshalYAML() (any, error) {
if t.Children == nil {
return t.Title.Value, nil
} else {
return map[string]any{t.Title.Value: t.Children}, nil
}
}

View File

@ -99,6 +99,8 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
Author: cmd.Options.Author, Author: cmd.Options.Author,
StripFirstDirectoryFromToc: cmd.Options.StripFirstDirectoryFromToc, StripFirstDirectoryFromToc: cmd.Options.StripFirstDirectoryFromToc,
Dry: cmd.Options.Dry, Dry: cmd.Options.Dry,
DryVerbose: cmd.Options.DryVerbose,
SortPathMode: cmd.Options.SortPathMode,
ImageOptions: &epub.ImageOptions{ ImageOptions: &epub.ImageOptions{
ViewWidth: profile.Width, ViewWidth: profile.Width,
ViewHeight: profile.Height, ViewHeight: profile.Height,