mirror of
https://github.com/celogeek/go-comic-converter.git
synced 2025-05-25 00:02:37 +02:00
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:
parent
105c21f8d2
commit
fbb48830d4
96
README.md
96
README.md
@ -127,6 +127,7 @@ Options:
|
||||
AddPanelView : false
|
||||
LimitMb : 200 Mb
|
||||
StripFirstDirectoryFromToc: true
|
||||
SortPathMode : path=alphanum, file=alpha
|
||||
|
||||
TOC:
|
||||
- mymanga
|
||||
@ -134,6 +135,58 @@ TOC:
|
||||
- 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
|
||||
|
||||
### Show current default option
|
||||
@ -156,6 +209,7 @@ Options:
|
||||
AddPanelView : false
|
||||
LimitMb : nolimit
|
||||
StripFirstDirectoryFromToc: false
|
||||
SortPathMode : path=alphanum, file=alpha
|
||||
```
|
||||
|
||||
### Change default settings
|
||||
@ -178,6 +232,7 @@ Options:
|
||||
AddPanelView : false
|
||||
LimitMb : 200 Mb
|
||||
StripFirstDirectoryFromToc: false
|
||||
SortPathMode : path=alphanum, file=alpha
|
||||
|
||||
Saving to /Users/vincent/.go-comic-converter.yaml
|
||||
```
|
||||
@ -202,43 +257,11 @@ Options:
|
||||
AddPanelView : false
|
||||
LimitMb : 200 Mb
|
||||
StripFirstDirectoryFromToc: false
|
||||
SortPathMode : path=alphanum, file=alpha
|
||||
|
||||
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
|
||||
To reset all value to default:
|
||||
|
||||
@ -283,6 +306,8 @@ Output:
|
||||
Number of workers
|
||||
-dry
|
||||
Dry run to show all options
|
||||
-dry-verbose
|
||||
Display also sorted files after the TOC
|
||||
|
||||
Config:
|
||||
-profile string
|
||||
@ -337,6 +362,11 @@ Config:
|
||||
Limit size of the ePub: Default nolimit (0), Minimum 20
|
||||
-strip
|
||||
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:
|
||||
-show
|
||||
|
@ -81,6 +81,7 @@ func (c *Converter) InitParse() {
|
||||
c.AddStringParam(&c.Options.Title, "title", "", "Title of the epub")
|
||||
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.DryVerbose, "dry-verbose", false, "Display also sorted files after the TOC")
|
||||
|
||||
c.AddSection("Config")
|
||||
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.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.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.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")
|
||||
}
|
||||
|
||||
if c.Options.SortPathMode < 0 || c.Options.SortPathMode > 2 {
|
||||
return errors.New("sort should be 0, 1 or 2")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -11,13 +11,14 @@ import (
|
||||
|
||||
type Options struct {
|
||||
// Output
|
||||
Input string `yaml:"-"`
|
||||
Output string `yaml:"-"`
|
||||
Author string `yaml:"-"`
|
||||
Title string `yaml:"-"`
|
||||
Auto bool `yaml:"-"`
|
||||
Workers int `yaml:"-"`
|
||||
Dry bool `yaml:"-"`
|
||||
Input string `yaml:"-"`
|
||||
Output string `yaml:"-"`
|
||||
Author string `yaml:"-"`
|
||||
Title string `yaml:"-"`
|
||||
Auto bool `yaml:"-"`
|
||||
Workers int `yaml:"-"`
|
||||
Dry bool `yaml:"-"`
|
||||
DryVerbose bool `yaml:"-"`
|
||||
|
||||
// Config
|
||||
Profile string `yaml:"profile"`
|
||||
@ -33,6 +34,7 @@ type Options struct {
|
||||
AddPanelView bool `yaml:"add_panel_view"`
|
||||
LimitMb int `yaml:"limit_mb"`
|
||||
StripFirstDirectoryFromToc bool `yaml:"strip_first_directory_from_toc"`
|
||||
SortPathMode int `yaml:"sort_path_mode"`
|
||||
|
||||
// Default Config
|
||||
Show bool `yaml:"-"`
|
||||
@ -62,6 +64,7 @@ func New() *Options {
|
||||
AddPanelView: false,
|
||||
LimitMb: 0,
|
||||
StripFirstDirectoryFromToc: false,
|
||||
SortPathMode: 1,
|
||||
profiles: profiles.New(),
|
||||
}
|
||||
}
|
||||
@ -127,6 +130,16 @@ func (o *Options) ShowDefault() string {
|
||||
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(`
|
||||
Profile : %s
|
||||
Quality : %d
|
||||
@ -140,7 +153,8 @@ func (o *Options) ShowDefault() string {
|
||||
HasCover : %v
|
||||
AddPanelView : %v
|
||||
LimitMb : %s
|
||||
StripFirstDirectoryFromToc: %v`,
|
||||
StripFirstDirectoryFromToc: %v
|
||||
SortPathMode : %s`,
|
||||
profileDesc,
|
||||
o.Quality,
|
||||
o.Crop,
|
||||
@ -154,6 +168,7 @@ func (o *Options) ShowDefault() string {
|
||||
o.AddPanelView,
|
||||
limitmb,
|
||||
o.StripFirstDirectoryFromToc,
|
||||
sortpathmode,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ImageOptions struct {
|
||||
@ -42,6 +41,8 @@ type EpubOptions struct {
|
||||
LimitMb int
|
||||
StripFirstDirectoryFromToc bool
|
||||
Dry bool
|
||||
DryVerbose bool
|
||||
SortPathMode int
|
||||
|
||||
*ImageOptions
|
||||
}
|
||||
@ -97,7 +98,7 @@ func (e *ePub) render(templateString string, data any) string {
|
||||
}
|
||||
|
||||
func (e *ePub) getParts() ([]*epubPart, error) {
|
||||
images, err := LoadImages(e.Input, e.ImageOptions, e.Dry)
|
||||
images, err := e.LoadImages()
|
||||
|
||||
if err != nil {
|
||||
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 {
|
||||
type zipContent struct {
|
||||
Name string
|
||||
@ -213,10 +240,9 @@ func (e *ePub) Write() error {
|
||||
}
|
||||
|
||||
if e.Dry {
|
||||
tocChildren := e.getToc(epubParts[0].Images)
|
||||
fmt.Fprintf(os.Stderr, "TOC:\n- %s\n", e.Title)
|
||||
if tocChildren != nil {
|
||||
yaml.NewEncoder(os.Stderr).Encode(tocChildren)
|
||||
fmt.Fprintf(os.Stderr, "TOC:\n- %s\n%s\n", e.Title, e.getTree(epubParts[0].Images, true))
|
||||
if e.DryVerbose {
|
||||
fmt.Fprintf(os.Stderr, "\nFiles:\n%s\n", e.getTree(epubParts[0].Images, false))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -34,13 +34,14 @@ type Image struct {
|
||||
IsCover bool
|
||||
NeedSpace bool
|
||||
Path string
|
||||
Name string
|
||||
}
|
||||
|
||||
type imageTask struct {
|
||||
Id int
|
||||
Reader io.ReadCloser
|
||||
Path string
|
||||
Filename string
|
||||
Id int
|
||||
Reader io.ReadCloser
|
||||
Path string
|
||||
Name string
|
||||
}
|
||||
|
||||
func colorIsBlank(c color.Color) bool {
|
||||
@ -94,10 +95,10 @@ BOTTOM:
|
||||
return imgArea
|
||||
}
|
||||
|
||||
func LoadImages(path string, options *ImageOptions, dry bool) ([]*Image, error) {
|
||||
func (e *ePub) LoadImages() ([]*Image, error) {
|
||||
images := make([]*Image, 0)
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
fi, err := os.Stat(e.Input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -108,15 +109,15 @@ func LoadImages(path string, options *ImageOptions, dry bool) ([]*Image, error)
|
||||
)
|
||||
|
||||
if fi.IsDir() {
|
||||
imageCount, imageInput, err = loadDir(path)
|
||||
imageCount, imageInput, err = loadDir(e.Input, e.SortPathMode)
|
||||
} else {
|
||||
switch ext := strings.ToLower(filepath.Ext(path)); ext {
|
||||
switch ext := strings.ToLower(filepath.Ext(e.Input)); ext {
|
||||
case ".cbz", ".zip":
|
||||
imageCount, imageInput, err = loadCbz(path)
|
||||
imageCount, imageInput, err = loadCbz(e.Input, e.SortPathMode)
|
||||
case ".cbr", ".rar":
|
||||
imageCount, imageInput, err = loadCbr(path)
|
||||
imageCount, imageInput, err = loadCbr(e.Input, e.SortPathMode)
|
||||
case ".pdf":
|
||||
imageCount, imageInput, err = loadPdf(path)
|
||||
imageCount, imageInput, err = loadPdf(e.Input)
|
||||
default:
|
||||
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
|
||||
}
|
||||
|
||||
if dry {
|
||||
if e.Dry {
|
||||
for img := range imageInput {
|
||||
img.Reader.Close()
|
||||
images = append(images, &Image{
|
||||
img.Id,
|
||||
0,
|
||||
nil,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
false, // NeedSpace reajust during parts computation
|
||||
img.Path,
|
||||
Id: img.Id,
|
||||
Part: 0,
|
||||
Data: nil,
|
||||
Width: 0,
|
||||
Height: 0,
|
||||
IsCover: false,
|
||||
NeedSpace: false, // NeedSpace reajust during parts computation
|
||||
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)
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
for i := 0; i < options.Workers; i++ {
|
||||
for i := 0; i < e.ImageOptions.Workers; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
@ -156,58 +159,61 @@ func LoadImages(path string, options *ImageOptions, dry bool) ([]*Image, error)
|
||||
for img := range imageInput {
|
||||
// Decode image
|
||||
src, _, err := image.Decode(img.Reader)
|
||||
img.Reader.Close()
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
if options.Crop {
|
||||
if e.ImageOptions.Crop {
|
||||
g := gift.New(gift.Crop(findMarging(src)))
|
||||
newSrc := image.NewNRGBA(g.Bounds(src.Bounds()))
|
||||
g.Draw(newSrc, src)
|
||||
src = newSrc
|
||||
}
|
||||
|
||||
g := NewGift(options)
|
||||
g := NewGift(e.ImageOptions)
|
||||
|
||||
// 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)
|
||||
|
||||
imageOutput <- &Image{
|
||||
img.Id,
|
||||
0,
|
||||
newImageData(img.Id, 0, dst, options.Quality),
|
||||
dst.Bounds().Dx(),
|
||||
dst.Bounds().Dy(),
|
||||
img.Id == 0,
|
||||
false,
|
||||
img.Path,
|
||||
Id: img.Id,
|
||||
Part: 0,
|
||||
Data: newImageData(img.Id, 0, dst, e.ImageOptions.Quality),
|
||||
Width: dst.Bounds().Dx(),
|
||||
Height: dst.Bounds().Dy(),
|
||||
IsCover: img.Id == 0,
|
||||
NeedSpace: false,
|
||||
Path: img.Path,
|
||||
Name: img.Name,
|
||||
}
|
||||
|
||||
// Auto split double page
|
||||
// Except for cover
|
||||
// Only if the src image have width > height and is bigger than the view
|
||||
if (!options.HasCover || img.Id > 0) &&
|
||||
options.AutoSplitDoublePage &&
|
||||
if (!e.ImageOptions.HasCover || img.Id > 0) &&
|
||||
e.ImageOptions.AutoSplitDoublePage &&
|
||||
src.Bounds().Dx() > src.Bounds().Dy() &&
|
||||
src.Bounds().Dx() > options.ViewHeight &&
|
||||
src.Bounds().Dy() > options.ViewWidth {
|
||||
gifts := NewGiftSplitDoublePage(options)
|
||||
src.Bounds().Dx() > e.ImageOptions.ViewHeight &&
|
||||
src.Bounds().Dy() > e.ImageOptions.ViewWidth {
|
||||
gifts := NewGiftSplitDoublePage(e.ImageOptions)
|
||||
for i, g := range gifts {
|
||||
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)
|
||||
imageOutput <- &Image{
|
||||
img.Id,
|
||||
part,
|
||||
newImageData(img.Id, part, dst, options.Quality),
|
||||
dst.Bounds().Dx(),
|
||||
dst.Bounds().Dy(),
|
||||
false,
|
||||
false, // NeedSpace reajust during parts computation
|
||||
img.Path,
|
||||
Id: img.Id,
|
||||
Part: part,
|
||||
Data: newImageData(img.Id, part, dst, e.ImageOptions.Quality),
|
||||
Width: dst.Bounds().Dx(),
|
||||
Height: dst.Bounds().Dy(),
|
||||
IsCover: false,
|
||||
NeedSpace: false, // NeedSpace reajust during parts computation
|
||||
Path: img.Path,
|
||||
Name: img.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -221,7 +227,7 @@ func LoadImages(path string, options *ImageOptions, dry bool) ([]*Image, error)
|
||||
}()
|
||||
|
||||
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)
|
||||
}
|
||||
if image.Part == 0 {
|
||||
@ -247,7 +253,7 @@ func isSupportedImage(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func loadDir(input string) (int, chan *imageTask, error) {
|
||||
func loadDir(input string, sortpathmode int) (int, chan *imageTask, error) {
|
||||
images := make([]string, 0)
|
||||
input = filepath.Clean(input)
|
||||
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")
|
||||
}
|
||||
|
||||
sort.Sort(sortpath.By(images))
|
||||
sort.Sort(sortpath.By(images, sortpathmode))
|
||||
|
||||
output := make(chan *imageTask)
|
||||
go func() {
|
||||
@ -278,24 +284,25 @@ func loadDir(input string) (int, chan *imageTask, error) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
p := filepath.Dir(img)
|
||||
|
||||
p, fn := filepath.Split(img)
|
||||
if p == input {
|
||||
p = ""
|
||||
} else {
|
||||
p = p[len(input)+1:]
|
||||
}
|
||||
output <- &imageTask{
|
||||
Id: i,
|
||||
Reader: f,
|
||||
Path: p,
|
||||
Filename: img,
|
||||
Id: i,
|
||||
Reader: f,
|
||||
Path: p,
|
||||
Name: fn,
|
||||
}
|
||||
}
|
||||
}()
|
||||
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)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
@ -316,7 +323,7 @@ func loadCbz(input string) (int, chan *imageTask, error) {
|
||||
for _, img := range images {
|
||||
names = append(names, img.Name)
|
||||
}
|
||||
sort.Sort(sortpath.By(names))
|
||||
sort.Sort(sortpath.By(names, sortpathmode))
|
||||
|
||||
indexedNames := make(map[string]int)
|
||||
for i, name := range names {
|
||||
@ -332,18 +339,19 @@ func loadCbz(input string) (int, chan *imageTask, error) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
p, fn := filepath.Split(filepath.Clean(img.Name))
|
||||
output <- &imageTask{
|
||||
Id: indexedNames[img.Name],
|
||||
Reader: f,
|
||||
Path: filepath.Dir(filepath.Clean(img.Name)),
|
||||
Filename: img.Name,
|
||||
Id: indexedNames[img.Name],
|
||||
Reader: f,
|
||||
Path: p,
|
||||
Name: fn,
|
||||
}
|
||||
}
|
||||
}()
|
||||
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
|
||||
rl, err := rardecode.OpenReader(input, "")
|
||||
if err != nil {
|
||||
@ -372,7 +380,7 @@ func loadCbr(input string) (int, chan *imageTask, error) {
|
||||
return 0, nil, fmt.Errorf("no images found")
|
||||
}
|
||||
|
||||
sort.Sort(sortpath.By(names))
|
||||
sort.Sort(sortpath.By(names, sortpathmode))
|
||||
|
||||
indexedNames := make(map[string]int)
|
||||
for i, name := range names {
|
||||
@ -401,11 +409,13 @@ func loadCbr(input string) (int, chan *imageTask, error) {
|
||||
b := bytes.NewBuffer([]byte{})
|
||||
io.Copy(b, r)
|
||||
|
||||
p, fn := filepath.Split(filepath.Clean(f.Name))
|
||||
|
||||
output <- &imageTask{
|
||||
Id: idx,
|
||||
Reader: io.NopCloser(b),
|
||||
Path: filepath.Dir(filepath.Clean(f.Name)),
|
||||
Filename: f.Name,
|
||||
Id: idx,
|
||||
Reader: io.NopCloser(b),
|
||||
Path: p,
|
||||
Name: fn,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -421,6 +431,7 @@ func loadPdf(input string) (int, chan *imageTask, error) {
|
||||
}
|
||||
|
||||
nbPages := len(pdf.Pages())
|
||||
pageFmt := fmt.Sprintf("page %%0%dd", len(fmt.Sprintf("%d", nbPages)))
|
||||
output := make(chan *imageTask)
|
||||
go func() {
|
||||
defer close(output)
|
||||
@ -438,10 +449,10 @@ func loadPdf(input string) (int, chan *imageTask, error) {
|
||||
}
|
||||
|
||||
output <- &imageTask{
|
||||
Id: i,
|
||||
Reader: io.NopCloser(b),
|
||||
Path: "/",
|
||||
Filename: fmt.Sprintf("page %d", i+1),
|
||||
Id: i,
|
||||
Reader: io.NopCloser(b),
|
||||
Path: "",
|
||||
Name: fmt.Sprintf(pageFmt, i+1),
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
@ -7,14 +7,19 @@ import (
|
||||
"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 {
|
||||
name string
|
||||
number float64
|
||||
fullname string
|
||||
name string
|
||||
number 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 {
|
||||
return a.number - b.number
|
||||
} else {
|
||||
@ -25,16 +30,19 @@ func (a part) Compare(b part) float64 {
|
||||
func parsePart(p string) part {
|
||||
r := split_path_regex.FindStringSubmatch(p)
|
||||
if len(r) == 0 {
|
||||
return part{p, 0}
|
||||
return part{p, p, 0}
|
||||
}
|
||||
n, err := strconv.ParseFloat(r[2], 64)
|
||||
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 = strings.TrimSuffix(pathname, string(filepath.Separator))
|
||||
ext := filepath.Ext(name)
|
||||
@ -42,9 +50,17 @@ func parse(filename string) []part {
|
||||
|
||||
f := []part{}
|
||||
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
|
||||
}
|
||||
|
||||
@ -74,10 +90,10 @@ func (b by) Swap(i, j int) {
|
||||
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{}
|
||||
for _, filename := range filenames {
|
||||
p = append(p, parse(filename))
|
||||
p = append(p, parse(filename, mode))
|
||||
}
|
||||
return by{filenames, p}
|
||||
}
|
||||
|
@ -15,20 +15,8 @@ type TocChildren struct {
|
||||
Tags []*TocPart
|
||||
}
|
||||
|
||||
func (t *TocChildren) MarshalYAML() (any, error) {
|
||||
return t.Tags, nil
|
||||
}
|
||||
|
||||
type TocPart struct {
|
||||
XMLName xml.Name `xml:"li"`
|
||||
Title TocTitle
|
||||
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
|
||||
}
|
||||
}
|
||||
|
2
main.go
2
main.go
@ -99,6 +99,8 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
|
||||
Author: cmd.Options.Author,
|
||||
StripFirstDirectoryFromToc: cmd.Options.StripFirstDirectoryFromToc,
|
||||
Dry: cmd.Options.Dry,
|
||||
DryVerbose: cmd.Options.DryVerbose,
|
||||
SortPathMode: cmd.Options.SortPathMode,
|
||||
ImageOptions: &epub.ImageOptions{
|
||||
ViewWidth: profile.Width,
|
||||
ViewHeight: profile.Height,
|
||||
|
Loading…
x
Reference in New Issue
Block a user