diff --git a/internal/converter/converter.go b/internal/converter/converter.go index f458b16..ef5c93e 100644 --- a/internal/converter/converter.go +++ b/internal/converter/converter.go @@ -8,6 +8,7 @@ It use goflag with additional feature: package converter import ( + "encoding/json" "errors" "flag" "fmt" @@ -155,6 +156,7 @@ func (c *Converter) InitParse() { 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.AddBoolParam(&c.Options.Quiet, "quiet", false, "Disable progress bar") + c.AddBoolParam(&c.Options.Json, "json", false, "Output progression and information in Json format") c.AddBoolParam(&c.Options.Version, "version", false, "Show current and available version") c.AddBoolParam(&c.Options.Help, "help", false, "Show this help message") } @@ -400,12 +402,24 @@ func (c *Converter) Fatal(err error) { func (c *Converter) Stats() { // Display elapse time and memory usage + elapse := time.Since(c.startAt).Round(time.Millisecond) var mem runtime.MemStats runtime.ReadMemStats(&mem) - fmt.Fprintf( - os.Stderr, - "Completed in %s, Memory usage %d Mb\n", - time.Since(c.startAt).Round(time.Millisecond), - mem.Sys/1024/1024, - ) + + if c.Options.Json { + json.NewEncoder(os.Stdout).Encode(map[string]any{ + "type": "stats", + "data": map[string]any{ + "elapse": elapse, + "memory_usage_mb": mem.Sys / 1024 / 1024, + }, + }) + } else { + fmt.Fprintf( + os.Stderr, + "Completed in %s, Memory usage %d Mb\n", + elapse, + mem.Sys/1024/1024, + ) + } } diff --git a/internal/converter/options/converter_options.go b/internal/converter/options/converter_options.go index 585a452..1447624 100644 --- a/internal/converter/options/converter_options.go +++ b/internal/converter/options/converter_options.go @@ -4,6 +4,7 @@ Manage options with default value from config. package options import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -69,6 +70,7 @@ type Options struct { Dry bool `yaml:"-"` DryVerbose bool `yaml:"-"` Quiet bool `yaml:"-"` + Json bool `yaml:"-"` Version bool `yaml:"-"` Help bool `yaml:"-"` @@ -116,13 +118,73 @@ func (o *Options) String() string { {"Title", o.Title}, {"Workers", o.Workers}, } { - b.WriteString(fmt.Sprintf("\n %-26s: %v", v.K, v.V)) + b.WriteString(fmt.Sprintf("\n %-32s: %v", v.K, v.V)) } b.WriteString(o.ShowConfig()) b.WriteRune('\n') return b.String() } +func (o *Options) MarshalJSON() ([]byte, error) { + out := map[string]any{ + "input": o.Input, + "output": o.Output, + "author": o.Author, + "title": o.Title, + "workers": o.Workers, + "profile": o.GetProfile(), + "format": o.Format, + "grayscale": o.Grayscale, + "crop": o.Crop, + "autocontrast": o.AutoContrast, + "autorotate": o.AutoRotate, + "noblankimage": o.NoBlankImage, + "manga": o.Manga, + "hascover": o.HasCover, + "stripfirstdirectoryfromtoc": o.StripFirstDirectoryFromToc, + "sortpathmode": o.SortPathMode, + "foregroundcolor": o.ForegroundColor, + "backgroundcolor": o.BackgroundColor, + "resize": !o.NoResize, + "aspectratio": o.AspectRatio, + "portraitonly": o.PortraitOnly, + "titlepage": o.TitlePage, + } + if o.Format == "jpeg" { + out["quality"] = o.Quality + } + if o.Grayscale { + out["grayscale_mode"] = o.GrayscaleMode + } + if o.Crop { + out["crop_ratio"] = map[string]any{ + "left": o.CropRatioLeft, + "right": o.CropRatioRight, + "up": o.CropRatioUp, + "bottom": o.CropRatioBottom, + } + } + if o.Brightness != 0 { + out["brightness"] = o.Brightness + } + if o.Contrast != 0 { + out["contrast"] = o.Contrast + } + if o.PortraitOnly || !o.AppleBookCompatibility { + out["autosplitdoublepage"] = o.AutoSplitDoublePage + if o.AutoSplitDoublePage { + out["keepdoublepageifsplitted"] = o.KeepDoublePageIfSplitted + } + } + if o.LimitMb != 0 { + out["limitmb"] = o.LimitMb + } + if !o.PortraitOnly { + out["applebookcompatibility"] = o.AppleBookCompatibility + } + return json.Marshal(out) +} + // Config file: ~/.go-comic-converter.yaml func (o *Options) FileName() string { home, _ := os.UserHomeDir() @@ -205,19 +267,19 @@ func (o *Options) ShowConfig() string { {"Grayscale", o.Grayscale, true}, {"Grayscale Mode", grayscaleMode, o.Grayscale}, {"Crop", o.Crop, true}, - {"CropRatio", 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}, {"Contrast", o.Contrast, o.Contrast != 0}, - {"AutoContrast", o.AutoContrast, true}, - {"AutoRotate", o.AutoRotate, true}, - {"AutoSplitDoublePage", o.AutoSplitDoublePage, o.PortraitOnly || !o.AppleBookCompatibility}, - {"KeepDoublePageIfSplitted", o.KeepDoublePageIfSplitted, (o.PortraitOnly || !o.AppleBookCompatibility) && o.AutoSplitDoublePage}, - {"NoBlankImage", o.NoBlankImage, true}, + {"Auto Contrast", o.AutoContrast, true}, + {"Auto Rotate", o.AutoRotate, true}, + {"Auto Split DoublePage", o.AutoSplitDoublePage, o.PortraitOnly || !o.AppleBookCompatibility}, + {"Keep DoublePage If Splitted", o.KeepDoublePageIfSplitted, (o.PortraitOnly || !o.AppleBookCompatibility) && o.AutoSplitDoublePage}, + {"No Blank Image", o.NoBlankImage, true}, {"Manga", o.Manga, true}, - {"HasCover", o.HasCover, true}, - {"LimitMb", fmt.Sprintf("%d Mb", o.LimitMb), o.LimitMb != 0}, - {"StripFirstDirectoryFromToc", o.StripFirstDirectoryFromToc, true}, - {"SortPathMode", sortpathmode, true}, + {"Has Cover", o.HasCover, true}, + {"Limit", fmt.Sprintf("%d Mb", o.LimitMb), o.LimitMb != 0}, + {"Strip First Directory From Toc", o.StripFirstDirectoryFromToc, true}, + {"Sort Path Mode", sortpathmode, true}, {"Foreground Color", fmt.Sprintf("#%s", o.ForegroundColor), true}, {"Background Color", fmt.Sprintf("#%s", o.BackgroundColor), true}, {"Resize", !o.NoResize, true}, @@ -227,7 +289,7 @@ func (o *Options) ShowConfig() string { {"Apple Book Compatibility", o.AppleBookCompatibility, !o.PortraitOnly}, } { if v.Condition { - b.WriteString(fmt.Sprintf("\n %-30s: %v", v.Key, v.Value)) + b.WriteString(fmt.Sprintf("\n %-32s: %v", v.Key, v.Value)) } } return b.String() diff --git a/internal/converter/profiles/converter_profiles.go b/internal/converter/profiles/converter_profiles.go index 09e0057..7cfe0a3 100644 --- a/internal/converter/profiles/converter_profiles.go +++ b/internal/converter/profiles/converter_profiles.go @@ -9,10 +9,10 @@ import ( ) type Profile struct { - Code string - Description string - Width int - Height int + Code string `json:"code"` + Description string `json:"description"` + Width int `json:"width"` + Height int `json:"height"` } type Profiles []Profile diff --git a/internal/epub/epub.go b/internal/epub/epub.go index d8d3de8..df4da11 100644 --- a/internal/epub/epub.go +++ b/internal/epub/epub.go @@ -369,6 +369,7 @@ func (e *ePub) Write() error { CurrentJob: 2, TotalJob: 2, Quiet: e.Quiet, + Json: e.Json, }) e.computeViewPort(epubParts) @@ -454,7 +455,9 @@ func (e *ePub) Write() error { bar.Add(1) } bar.Close() - fmt.Fprintln(os.Stderr) + if !e.Json { + fmt.Fprintln(os.Stderr) + } // display corrupted images hasError := false diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go index bcabab4..f0fde37 100644 --- a/internal/epub/imageprocessor/epub_image_processor.go +++ b/internal/epub/imageprocessor/epub_image_processor.go @@ -54,6 +54,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { // processing bar := epubprogress.New(epubprogress.Options{ Quiet: e.Quiet, + Json: e.Json, Max: imageCount, Description: "Processing", CurrentJob: 1, diff --git a/internal/epub/options/epub_options.go b/internal/epub/options/epub_options.go index 9ac548d..2de0277 100644 --- a/internal/epub/options/epub_options.go +++ b/internal/epub/options/epub_options.go @@ -53,6 +53,7 @@ type Options struct { DryVerbose bool SortPathMode int Quiet bool + Json bool Workers int Image *Image } diff --git a/internal/epub/progress/epub_progress.go b/internal/epub/progress/epub_progress.go index 8066718..f664f72 100644 --- a/internal/epub/progress/epub_progress.go +++ b/internal/epub/progress/epub_progress.go @@ -13,6 +13,7 @@ import ( type Options struct { Quiet bool + Json bool Max int Description string CurrentJob int @@ -21,13 +22,18 @@ type Options struct { type EpubProgress interface { Add(num int) error - Close() (err error) + Close() error } func New(o Options) EpubProgress { if o.Quiet { return progressbar.DefaultSilent(int64(o.Max)) } + + if o.Json { + return newEpubProgressJson(o) + } + fmtJob := fmt.Sprintf("%%0%dd", len(fmt.Sprint(o.TotalJob))) fmtDesc := fmt.Sprintf("[%s/%s] %%-15s", fmtJob, fmtJob) return progressbar.NewOptions(o.Max, diff --git a/internal/epub/progress/epub_progress_json.go b/internal/epub/progress/epub_progress_json.go new file mode 100644 index 0000000..fc4a46c --- /dev/null +++ b/internal/epub/progress/epub_progress_json.go @@ -0,0 +1,42 @@ +package epubprogress + +import ( + "encoding/json" + "os" +) + +type EpubProgressJson struct { + o Options + e *json.Encoder + current int +} + +func newEpubProgressJson(o Options) EpubProgress { + return &EpubProgressJson{ + o: o, + e: json.NewEncoder(os.Stdout), + } +} + +func (p *EpubProgressJson) Add(num int) error { + p.current += num + p.e.Encode(map[string]any{ + "type": "progress", + "data": map[string]any{ + "progress": map[string]any{ + "current": p.current, + "total": p.o.Max, + }, + "steps": map[string]any{ + "current": p.o.CurrentJob, + "total": p.o.TotalJob, + }, + "description": p.o.Description, + }, + }) + return nil +} + +func (p *EpubProgressJson) Close() error { + return nil +} diff --git a/main.go b/main.go index de25384..2d19725 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ EPUB is now support by Amazon through [SendToKindle](https://www.amazon.com/gp/s package main import ( + "encoding/json" "fmt" "os" "runtime/debug" @@ -96,7 +97,13 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s cmd.Fatal(err) } - fmt.Fprintln(os.Stderr, cmd.Options) + if cmd.Options.Json { + json.NewEncoder(os.Stdout).Encode(map[string]any{ + "type": "options", "data": cmd.Options, + }) + } else { + fmt.Fprintln(os.Stderr, cmd.Options) + } profile := cmd.Options.GetProfile() @@ -113,6 +120,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s Dry: cmd.Options.Dry, DryVerbose: cmd.Options.DryVerbose, Quiet: cmd.Options.Quiet, + Json: cmd.Options.Json, 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}, Quality: cmd.Options.Quality,