mirror of
https://github.com/celogeek/go-comic-converter.git
synced 2025-05-25 00:02:37 +02:00
354 lines
11 KiB
Go
354 lines
11 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"image/color"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/celogeek/go-comic-converter/internal/epub"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type Profile struct {
|
|
Code string
|
|
Description string
|
|
Width int
|
|
Height int
|
|
Palette color.Palette
|
|
}
|
|
|
|
var Profiles = []Profile{
|
|
// Kindle
|
|
{"K1", "Kindle 1", 600, 670, epub.PALETTE_4},
|
|
{"K11", "Kindle 11", 1072, 1448, epub.PALETTE_16},
|
|
{"K2", "Kindle 2", 600, 670, epub.PALETTE_15},
|
|
{"K34", "Kindle Keyboard/Touch", 600, 800, epub.PALETTE_16},
|
|
{"K578", "Kindle", 600, 800, epub.PALETTE_16},
|
|
{"KDX", "Kindle DX/DXG", 824, 1000, epub.PALETTE_16},
|
|
{"KPW", "Kindle Paperwhite 1/2", 758, 1024, epub.PALETTE_16},
|
|
{"KV", "Kindle Paperwhite 3/4/Voyage/Oasis", 1072, 1448, epub.PALETTE_16},
|
|
{"KPW5", "Kindle Paperwhite 5/Signature Edition", 1236, 1648, epub.PALETTE_16},
|
|
{"KO", "Kindle Oasis 2/3", 1264, 1680, epub.PALETTE_16},
|
|
{"KS", "Kindle Scribe", 1860, 2480, epub.PALETTE_16},
|
|
// Kobo
|
|
{"KoMT", "Kobo Mini/Touch", 600, 800, epub.PALETTE_16},
|
|
{"KoG", "Kobo Glo", 768, 1024, epub.PALETTE_16},
|
|
{"KoGHD", "Kobo Glo HD", 1072, 1448, epub.PALETTE_16},
|
|
{"KoA", "Kobo Aura", 758, 1024, epub.PALETTE_16},
|
|
{"KoAHD", "Kobo Aura HD", 1080, 1440, epub.PALETTE_16},
|
|
{"KoAH2O", "Kobo Aura H2O", 1080, 1430, epub.PALETTE_16},
|
|
{"KoAO", "Kobo Aura ONE", 1404, 1872, epub.PALETTE_16},
|
|
{"KoN", "Kobo Nia", 758, 1024, epub.PALETTE_16},
|
|
{"KoC", "Kobo Clara HD/Kobo Clara 2E", 1072, 1448, epub.PALETTE_16},
|
|
{"KoL", "Kobo Libra H2O/Kobo Libra 2", 1264, 1680, epub.PALETTE_16},
|
|
{"KoF", "Kobo Forma", 1440, 1920, epub.PALETTE_16},
|
|
{"KoS", "Kobo Sage", 1440, 1920, epub.PALETTE_16},
|
|
{"KoE", "Kobo Elipsa", 1404, 1872, epub.PALETTE_16},
|
|
}
|
|
var ProfilesIdx = map[string]int{}
|
|
|
|
func init() {
|
|
for i, p := range Profiles {
|
|
ProfilesIdx[p.Code] = i
|
|
}
|
|
}
|
|
|
|
var Home, _ = os.UserHomeDir()
|
|
var ConfigFile = filepath.Join(Home, ".go-comic-converter.yaml")
|
|
|
|
type Config struct {
|
|
Profile string `yaml:"profile"`
|
|
Quality int `yaml:"quality"`
|
|
Crop bool `yaml:"crop"`
|
|
Brightness int `yaml:"brightness"`
|
|
Contrast int `yaml:"contrast"`
|
|
AutoRotate bool `yaml:"auto_rotate"`
|
|
AutoSplitDoublePage bool `yaml:"auto_split_double_page"`
|
|
NoBlankPage bool `yaml:"no_blank_page"`
|
|
Manga bool `yaml:"manga"`
|
|
HasCover bool `yaml:"has_cover"`
|
|
AddPanelView bool `yaml:"add_panel_view"`
|
|
LimitMb int `yaml:"limit_mb"`
|
|
}
|
|
|
|
type Option struct {
|
|
Input string
|
|
Output string
|
|
Profile string
|
|
Author string
|
|
Title string
|
|
Quality int
|
|
Crop bool
|
|
Brightness int
|
|
Contrast int
|
|
Auto bool
|
|
AutoRotate bool
|
|
AutoSplitDoublePage bool
|
|
NoBlankPage bool
|
|
Manga bool
|
|
HasCover bool
|
|
AddPanelView bool
|
|
Workers int
|
|
LimitMb int
|
|
Save bool
|
|
}
|
|
|
|
func (o *Option) String() string {
|
|
var desc string
|
|
var width, height, level int
|
|
if i, ok := ProfilesIdx[o.Profile]; ok {
|
|
profile := Profiles[i]
|
|
desc = profile.Description
|
|
width = profile.Width
|
|
height = profile.Height
|
|
level = len(profile.Palette)
|
|
}
|
|
limitmb := "nolimit"
|
|
if o.LimitMb > 0 {
|
|
limitmb = fmt.Sprintf("%d Mb", o.LimitMb)
|
|
}
|
|
|
|
return fmt.Sprintf(`Go Comic Converter
|
|
|
|
Options:
|
|
Input : %s
|
|
Output : %s
|
|
Profile : %s - %s - %dx%d - %d levels of gray
|
|
Author : %s
|
|
Title : %s
|
|
Quality : %d
|
|
Crop : %v
|
|
Brightness : %d
|
|
Contrast : %d
|
|
AutoRotate : %v
|
|
AutoSplitDoublePage: %v
|
|
NoBlankPage : %v
|
|
Manga : %v
|
|
HasCover : %v
|
|
AddPanelView : %v
|
|
LimitMb : %s
|
|
Workers : %d
|
|
`,
|
|
o.Input,
|
|
o.Output,
|
|
o.Profile, desc, width, height, level,
|
|
o.Author,
|
|
o.Title,
|
|
o.Quality,
|
|
o.Crop,
|
|
o.Brightness,
|
|
o.Contrast,
|
|
o.AutoRotate,
|
|
o.AutoSplitDoublePage,
|
|
o.NoBlankPage,
|
|
o.Manga,
|
|
o.HasCover,
|
|
o.AddPanelView,
|
|
limitmb,
|
|
o.Workers,
|
|
)
|
|
}
|
|
|
|
func main() {
|
|
availableProfiles := make([]string, 0)
|
|
for _, p := range Profiles {
|
|
availableProfiles = append(availableProfiles, fmt.Sprintf(
|
|
" - %-7s ( %9s ) - %2d levels of gray - %s",
|
|
p.Code,
|
|
fmt.Sprintf("%dx%d", p.Width, p.Height),
|
|
len(p.Palette),
|
|
p.Description,
|
|
))
|
|
}
|
|
|
|
defaultOpt := &Config{
|
|
Profile: "",
|
|
Quality: 85,
|
|
Crop: true,
|
|
Brightness: 0,
|
|
Contrast: 0,
|
|
AutoRotate: false,
|
|
AutoSplitDoublePage: false,
|
|
NoBlankPage: false,
|
|
Manga: false,
|
|
HasCover: true,
|
|
AddPanelView: false,
|
|
LimitMb: 0,
|
|
}
|
|
configHandler, err := os.Open(ConfigFile)
|
|
if err == nil {
|
|
defer configHandler.Close()
|
|
err = yaml.NewDecoder(configHandler).Decode(defaultOpt)
|
|
if err != nil && err.Error() != "EOF" {
|
|
fmt.Fprintf(os.Stderr, "Error detected in your config %q\n", ConfigFile)
|
|
fmt.Fprint(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
opt := &Option{}
|
|
flag.StringVar(&opt.Input, "input", "", "Source of comic to convert: directory, cbz, zip, cbr, rar, pdf")
|
|
flag.StringVar(&opt.Output, "output", "", "Output of the epub (directory or epub): (default [INPUT].epub)")
|
|
flag.StringVar(&opt.Profile, "profile", defaultOpt.Profile, fmt.Sprintf("Profile to use: \n%s\n", strings.Join(availableProfiles, "\n")))
|
|
flag.StringVar(&opt.Author, "author", "GO Comic Converter", "Author of the epub")
|
|
flag.StringVar(&opt.Title, "title", "", "Title of the epub")
|
|
flag.IntVar(&opt.Quality, "quality", defaultOpt.Quality, "Quality of the image")
|
|
flag.BoolVar(&opt.Crop, "crop", defaultOpt.Crop, "Crop images")
|
|
flag.IntVar(&opt.Brightness, "brightness", defaultOpt.Brightness, "Brightness readjustement: between -100 and 100, > 0 lighter, < 0 darker")
|
|
flag.IntVar(&opt.Contrast, "contrast", defaultOpt.Contrast, "Contrast readjustement: between -100 and 100, > 0 more contrast, < 0 less contrast")
|
|
flag.BoolVar(&opt.Auto, "auto", false, "Activate all automatic options")
|
|
flag.BoolVar(&opt.AutoRotate, "autorotate", defaultOpt.AutoRotate, "Auto Rotate page when width > height")
|
|
flag.BoolVar(&opt.AutoSplitDoublePage, "autosplitdoublepage", defaultOpt.AutoSplitDoublePage, "Auto Split double page when width > height")
|
|
flag.BoolVar(&opt.NoBlankPage, "noblankpage", defaultOpt.NoBlankPage, "Remove blank pages")
|
|
flag.BoolVar(&opt.Manga, "manga", defaultOpt.Manga, "Manga mode (right to left)")
|
|
flag.BoolVar(&opt.HasCover, "hascover", defaultOpt.HasCover, "Has cover. Indicate if your comic have a cover. The first page will be used as a cover and include after the title.")
|
|
flag.BoolVar(&opt.AddPanelView, "addpanelview", defaultOpt.AddPanelView, "Add an embeded panel view. On kindle you may not need this option as it is handled by the kindle.")
|
|
flag.IntVar(&opt.LimitMb, "limitmb", defaultOpt.LimitMb, "Limit size of the ePub: Default nolimit (0), Minimum 20")
|
|
flag.IntVar(&opt.Workers, "workers", runtime.NumCPU(), "Number of workers")
|
|
flag.BoolVar(&opt.Save, "save", false, "Save your parameters as default.")
|
|
flag.Usage = func() {
|
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", filepath.Base(os.Args[0]))
|
|
flag.PrintDefaults()
|
|
}
|
|
flag.Parse()
|
|
|
|
if opt.Save {
|
|
f, err := os.Create(ConfigFile)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
defer f.Close()
|
|
yaml.NewEncoder(f).Encode(&Config{
|
|
Profile: opt.Profile,
|
|
Quality: opt.Quality,
|
|
Crop: opt.Crop,
|
|
Brightness: opt.Brightness,
|
|
Contrast: opt.Contrast,
|
|
AutoRotate: opt.AutoRotate,
|
|
AutoSplitDoublePage: opt.AutoSplitDoublePage,
|
|
NoBlankPage: opt.NoBlankPage,
|
|
Manga: opt.Manga,
|
|
HasCover: opt.HasCover,
|
|
AddPanelView: opt.AddPanelView,
|
|
LimitMb: opt.LimitMb,
|
|
})
|
|
fmt.Fprintf(os.Stderr, "Default settings saved in %q\n", ConfigFile)
|
|
os.Exit(0)
|
|
}
|
|
|
|
if opt.Input == "" {
|
|
fmt.Fprintln(os.Stderr, "Missing input or output!")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
var defaultOutput string
|
|
fi, err := os.Stat(opt.Input)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
inputBase := filepath.Clean(opt.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 opt.Output == "" {
|
|
opt.Output = defaultOutput
|
|
}
|
|
|
|
if filepath.Ext(opt.Output) != ".epub" {
|
|
fo, err := os.Stat(opt.Output)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
if !fo.IsDir() {
|
|
fmt.Fprintln(os.Stderr, "output must be an existing dir or end with .epub")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
opt.Output = filepath.Join(
|
|
opt.Output,
|
|
filepath.Base(defaultOutput),
|
|
)
|
|
}
|
|
|
|
profileIdx, profileMatch := ProfilesIdx[opt.Profile]
|
|
if !profileMatch {
|
|
fmt.Fprintf(os.Stderr, "Profile %q doesn't exists!\n", opt.Profile)
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
profile := Profiles[profileIdx]
|
|
|
|
if opt.LimitMb > 0 && opt.LimitMb < 20 {
|
|
fmt.Fprintln(os.Stderr, "LimitMb should be 0 or >= 20")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if opt.Brightness < -100 || opt.Brightness > 100 {
|
|
fmt.Fprintln(os.Stderr, "Brightness should be between -100 and 100")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if opt.Contrast < -100 || opt.Contrast > 100 {
|
|
fmt.Fprintln(os.Stderr, "Contrast should be between -100 and 100")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if opt.Title == "" {
|
|
ext := filepath.Ext(defaultOutput)
|
|
opt.Title = filepath.Base(defaultOutput[0 : len(defaultOutput)-len(ext)])
|
|
}
|
|
|
|
if opt.Auto {
|
|
opt.AutoRotate = true
|
|
opt.AutoSplitDoublePage = true
|
|
}
|
|
|
|
fmt.Fprintln(os.Stderr, opt)
|
|
|
|
if err := epub.NewEpub(&epub.EpubOptions{
|
|
Input: opt.Input,
|
|
Output: opt.Output,
|
|
LimitMb: opt.LimitMb,
|
|
Title: opt.Title,
|
|
Author: opt.Author,
|
|
ImageOptions: &epub.ImageOptions{
|
|
ViewWidth: profile.Width,
|
|
ViewHeight: profile.Height,
|
|
Quality: opt.Quality,
|
|
Crop: opt.Crop,
|
|
Palette: profile.Palette,
|
|
Brightness: opt.Brightness,
|
|
Contrast: opt.Contrast,
|
|
AutoRotate: opt.AutoRotate,
|
|
AutoSplitDoublePage: opt.AutoSplitDoublePage,
|
|
NoBlankPage: opt.NoBlankPage,
|
|
Manga: opt.Manga,
|
|
HasCover: opt.HasCover,
|
|
AddPanelView: opt.AddPanelView,
|
|
Workers: opt.Workers,
|
|
},
|
|
}).Write(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|