mirror of
https://github.com/celogeek/go-comic-converter.git
synced 2025-05-25 08:12:36 +02:00
271 lines
8.7 KiB
Go
271 lines
8.7 KiB
Go
package converter
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/celogeek/go-comic-converter/v2/internal/converter/options"
|
|
)
|
|
|
|
type Converter struct {
|
|
Options *options.Options
|
|
Cmd *flag.FlagSet
|
|
|
|
order []Order
|
|
isZeroValueErrs []error
|
|
}
|
|
|
|
func New() *Converter {
|
|
options := options.New()
|
|
cmd := flag.NewFlagSet("go-comic-converter", flag.ExitOnError)
|
|
conv := &Converter{
|
|
Options: options,
|
|
Cmd: cmd,
|
|
order: make([]Order, 0),
|
|
}
|
|
|
|
cmdOutput := &strings.Builder{}
|
|
cmd.SetOutput(cmdOutput)
|
|
cmd.Usage = func() {
|
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", filepath.Base(os.Args[0]))
|
|
for _, o := range conv.order {
|
|
switch v := o.(type) {
|
|
case OrderSection:
|
|
fmt.Fprintf(os.Stderr, "\n%s:\n", o.Value())
|
|
case OrderName:
|
|
fmt.Fprintln(os.Stderr, conv.Usage(v.isString, cmd.Lookup(v.Value())))
|
|
}
|
|
}
|
|
if cmdOutput.Len() > 0 {
|
|
fmt.Fprintf(os.Stderr, "\nError: %s", cmdOutput.String())
|
|
}
|
|
}
|
|
|
|
return conv
|
|
}
|
|
|
|
func (c *Converter) LoadConfig() error {
|
|
return c.Options.LoadDefault()
|
|
}
|
|
|
|
func (c *Converter) AddSection(section string) {
|
|
c.order = append(c.order, OrderSection{value: section})
|
|
}
|
|
|
|
func (c *Converter) AddStringParam(p *string, name string, value string, usage string) {
|
|
c.Cmd.StringVar(p, name, value, usage)
|
|
c.order = append(c.order, OrderName{value: name, isString: true})
|
|
}
|
|
|
|
func (c *Converter) AddIntParam(p *int, name string, value int, usage string) {
|
|
c.Cmd.IntVar(p, name, value, usage)
|
|
c.order = append(c.order, OrderName{value: name})
|
|
}
|
|
|
|
func (c *Converter) AddBoolParam(p *bool, name string, value bool, usage string) {
|
|
c.Cmd.BoolVar(p, name, value, usage)
|
|
c.order = append(c.order, OrderName{value: name})
|
|
}
|
|
|
|
func (c *Converter) InitParse() {
|
|
c.AddSection("Output")
|
|
c.AddStringParam(&c.Options.Input, "input", "", "Source of comic to convert: directory, cbz, zip, cbr, rar, pdf")
|
|
c.AddStringParam(&c.Options.Output, "output", "", "Output of the epub (directory or epub): (default [INPUT].epub)")
|
|
c.AddStringParam(&c.Options.Author, "author", "GO Comic Converter", "Author 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.AddBoolParam(&c.Options.Dry, "dry", false, "Dry run to show all options")
|
|
|
|
c.AddSection("Config")
|
|
c.AddStringParam(&c.Options.Profile, "profile", c.Options.Profile, fmt.Sprintf("Profile to use: \n%s", c.Options.AvailableProfiles()))
|
|
c.AddIntParam(&c.Options.Quality, "quality", c.Options.Quality, "Quality of the image")
|
|
c.AddBoolParam(&c.Options.Crop, "crop", c.Options.Crop, "Crop images")
|
|
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 readjustement: between -100 and 100, > 0 more contrast, < 0 less contrast")
|
|
c.AddBoolParam(&c.Options.AutoRotate, "autorotate", c.Options.AutoRotate, "Auto Rotate page when width > height")
|
|
c.AddBoolParam(&c.Options.Auto, "auto", false, "Activate all automatic options")
|
|
c.AddBoolParam(&c.Options.AutoSplitDoublePage, "autosplitdoublepage", c.Options.AutoSplitDoublePage, "Auto Split double page when width > height")
|
|
c.AddBoolParam(&c.Options.NoBlankPage, "noblankpage", c.Options.NoBlankPage, "Remove blank pages")
|
|
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.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.AddSection("Default config")
|
|
c.AddBoolParam(&c.Options.Show, "show", false, "Show your default parameters")
|
|
c.AddBoolParam(&c.Options.Save, "save", false, "Save your parameters as default")
|
|
c.AddBoolParam(&c.Options.Reset, "reset", false, "Reset your parameters to default")
|
|
|
|
c.AddSection("Other")
|
|
c.AddBoolParam(&c.Options.Version, "version", false, "Show current and available version")
|
|
c.AddBoolParam(&c.Options.Help, "help", false, "Show this help message")
|
|
}
|
|
|
|
func (c *Converter) Usage(isString bool, f *flag.Flag) string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, " -%s", f.Name) // Two spaces before -; see next two comments.
|
|
name, usage := flag.UnquoteUsage(f)
|
|
if len(name) > 0 {
|
|
b.WriteString(" ")
|
|
b.WriteString(name)
|
|
}
|
|
// Print the default value only if it differs to the zero value
|
|
// for this flag type.
|
|
if isZero, err := c.isZeroValue(f, f.DefValue); err != nil {
|
|
c.isZeroValueErrs = append(c.isZeroValueErrs, err)
|
|
} else if !isZero {
|
|
if isString {
|
|
fmt.Fprintf(&b, " (default %q)", f.DefValue)
|
|
} else {
|
|
fmt.Fprintf(&b, " (default %v)", f.DefValue)
|
|
}
|
|
}
|
|
|
|
// Boolean flags of one ASCII letter are so common we
|
|
// treat them specially, putting their usage on the same line.
|
|
if b.Len() <= 4 { // space, space, '-', 'x'.
|
|
b.WriteString("\t")
|
|
} else {
|
|
// Four spaces before the tab triggers good alignment
|
|
// for both 4- and 8-space tab stops.
|
|
b.WriteString("\n \t")
|
|
}
|
|
b.WriteString(strings.ReplaceAll(usage, "\n", "\n \t"))
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// isZeroValue determines whether the string represents the zero
|
|
// value for a flag.
|
|
func (c *Converter) isZeroValue(f *flag.Flag, value string) (ok bool, err error) {
|
|
// Build a zero value of the flag's Value type, and see if the
|
|
// result of calling its String method equals the value passed in.
|
|
// This works unless the Value type is itself an interface type.
|
|
typ := reflect.TypeOf(f.Value)
|
|
var z reflect.Value
|
|
if typ.Kind() == reflect.Pointer {
|
|
z = reflect.New(typ.Elem())
|
|
} else {
|
|
z = reflect.Zero(typ)
|
|
}
|
|
// Catch panics calling the String method, which shouldn't prevent the
|
|
// usage message from being printed, but that we should report to the
|
|
// user so that they know to fix their code.
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
if typ.Kind() == reflect.Pointer {
|
|
typ = typ.Elem()
|
|
}
|
|
err = fmt.Errorf("panic calling String method on zero %v for flag %s: %v", typ, f.Name, e)
|
|
}
|
|
}()
|
|
return value == z.Interface().(flag.Value).String(), nil
|
|
}
|
|
|
|
func (c *Converter) Parse() {
|
|
c.Cmd.Parse(os.Args[1:])
|
|
if c.Options.Help {
|
|
c.Cmd.Usage()
|
|
os.Exit(0)
|
|
}
|
|
|
|
if c.Options.Auto {
|
|
c.Options.AutoRotate = true
|
|
c.Options.AutoSplitDoublePage = true
|
|
}
|
|
}
|
|
|
|
func (c *Converter) Validate() error {
|
|
// Check input
|
|
if c.Options.Input == "" {
|
|
return errors.New("missing input")
|
|
}
|
|
|
|
fi, err := os.Stat(c.Options.Input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check Output
|
|
var defaultOutput string
|
|
inputBase := filepath.Clean(c.Options.Input)
|
|
if fi.IsDir() {
|
|
defaultOutput = fmt.Sprintf("%s.epub", inputBase)
|
|
} else {
|
|
ext := filepath.Ext(inputBase)
|
|
defaultOutput = fmt.Sprintf("%s.epub", inputBase[0:len(inputBase)-len(ext)])
|
|
}
|
|
|
|
if c.Options.Output == "" {
|
|
c.Options.Output = defaultOutput
|
|
}
|
|
|
|
c.Options.Output = filepath.Clean(c.Options.Output)
|
|
if filepath.Ext(c.Options.Output) == ".epub" {
|
|
fo, err := os.Stat(filepath.Dir(c.Options.Output))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !fo.IsDir() {
|
|
return errors.New("parent of the output is not a directory")
|
|
}
|
|
} else {
|
|
fo, err := os.Stat(c.Options.Output)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !fo.IsDir() {
|
|
return errors.New("output must be an existing dir or end with .epub")
|
|
}
|
|
c.Options.Output = filepath.Join(
|
|
c.Options.Output,
|
|
filepath.Base(defaultOutput),
|
|
)
|
|
}
|
|
|
|
// Title
|
|
if c.Options.Title == "" {
|
|
ext := filepath.Ext(defaultOutput)
|
|
c.Options.Title = filepath.Base(defaultOutput[0 : len(defaultOutput)-len(ext)])
|
|
}
|
|
|
|
// Profile
|
|
if c.Options.Profile == "" {
|
|
return errors.New("profile missing")
|
|
}
|
|
|
|
if p := c.Options.GetProfile(); p == nil {
|
|
return fmt.Errorf("profile %q doesn't exists", c.Options.Profile)
|
|
}
|
|
|
|
// LimitMb
|
|
if c.Options.LimitMb < 20 && c.Options.LimitMb != 0 {
|
|
return errors.New("limitmb should be 0 or >= 20")
|
|
}
|
|
|
|
// Brightness
|
|
if c.Options.Brightness < -100 || c.Options.Brightness > 100 {
|
|
return errors.New("brightness should be between -100 and 100")
|
|
}
|
|
|
|
// Contrast
|
|
if c.Options.Contrast < -100 || c.Options.Contrast > 100 {
|
|
return errors.New("contrast should be between -100 and 100")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c Converter) Fatal(err error) {
|
|
c.Cmd.Usage()
|
|
fmt.Fprintf(os.Stderr, "\nError: %s\n", err)
|
|
os.Exit(1)
|
|
}
|