mirror of
https://github.com/gen2brain/cbconvert
synced 2026-06-30 09:11:54 +02:00
Fix outdir and recursive, issue #41
This commit is contained in:
+39
-11
@@ -93,6 +93,8 @@ type Converter struct {
|
|||||||
Workdir string
|
Workdir string
|
||||||
// Page name prefix, set per input when combining
|
// Page name prefix, set per input when combining
|
||||||
prefix string
|
prefix string
|
||||||
|
// Input root for the current file, used to build recursive output paths
|
||||||
|
root string
|
||||||
// Number of files
|
// Number of files
|
||||||
Nfiles int
|
Nfiles int
|
||||||
// Index of the current file
|
// Index of the current file
|
||||||
@@ -115,6 +117,7 @@ type Converter struct {
|
|||||||
type File struct {
|
type File struct {
|
||||||
Name string
|
Name string
|
||||||
Path string
|
Path string
|
||||||
|
Root string
|
||||||
Stat os.FileInfo
|
Stat os.FileInfo
|
||||||
SizeHuman string
|
SizeHuman string
|
||||||
}
|
}
|
||||||
@@ -157,11 +160,13 @@ func (c *Converter) Cancel() {
|
|||||||
// Files returns list of found comic files.
|
// Files returns list of found comic files.
|
||||||
func (c *Converter) Files(args []string) ([]File, error) {
|
func (c *Converter) Files(args []string) ([]File, error) {
|
||||||
var files []File
|
var files []File
|
||||||
|
var root string
|
||||||
|
|
||||||
toFile := func(fp string, f os.FileInfo) File {
|
toFile := func(fp string, f os.FileInfo) File {
|
||||||
var file File
|
var file File
|
||||||
file.Name = filepath.Base(fp)
|
file.Name = filepath.Base(fp)
|
||||||
file.Path = fp
|
file.Path = fp
|
||||||
|
file.Root = root
|
||||||
file.Stat = f
|
file.Stat = f
|
||||||
file.SizeHuman = humanize.IBytes(uint64(f.Size()))
|
file.SizeHuman = humanize.IBytes(uint64(f.Size()))
|
||||||
return file
|
return file
|
||||||
@@ -214,12 +219,14 @@ func (c *Converter) Files(args []string) ([]File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !stat.IsDir() {
|
if !stat.IsDir() {
|
||||||
|
root = filepath.Dir(path)
|
||||||
if isArchive(path) || isDocument(path) {
|
if isArchive(path) || isDocument(path) {
|
||||||
if isSize(int64(c.Opts.Size), stat.Size()) {
|
if isSize(int64(c.Opts.Size), stat.Size()) {
|
||||||
files = append(files, toFile(path, stat))
|
files = append(files, toFile(path, stat))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
root = path
|
||||||
if c.Opts.Recursive {
|
if c.Opts.Recursive {
|
||||||
if err := filepath.Walk(path, walkFiles); err != nil {
|
if err := filepath.Walk(path, walkFiles); err != nil {
|
||||||
return files, fmt.Errorf("%s: %w", arg, err)
|
return files, fmt.Errorf("%s: %w", arg, err)
|
||||||
@@ -261,9 +268,26 @@ func (c *Converter) Files(args []string) ([]File, error) {
|
|||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recursiveDir mirrors the source path under OutDir, relative to the input root.
|
||||||
|
func (c *Converter) recursiveDir(fileName string) string {
|
||||||
|
dir := filepath.Dir(fileName)
|
||||||
|
|
||||||
|
if c.root != "" {
|
||||||
|
if rel, err := filepath.Rel(c.root, dir); err == nil {
|
||||||
|
return filepath.Join(c.Opts.OutDir, rel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir = strings.TrimPrefix(dir[len(filepath.VolumeName(dir)):], string(os.PathSeparator))
|
||||||
|
|
||||||
|
return filepath.Join(c.Opts.OutDir, dir)
|
||||||
|
}
|
||||||
|
|
||||||
// Cover extracts cover.
|
// Cover extracts cover.
|
||||||
func (c *Converter) Cover(fileName string, fileInfo os.FileInfo) error {
|
func (c *Converter) Cover(file File) error {
|
||||||
c.CurrFile++
|
c.CurrFile++
|
||||||
|
c.root = file.Root
|
||||||
|
fileName, fileInfo := file.Path, file.Stat
|
||||||
|
|
||||||
cover, err := c.coverImage(fileName, fileInfo)
|
cover, err := c.coverImage(fileName, fileInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -285,13 +309,12 @@ func (c *Converter) Cover(fileName string, fileInfo os.FileInfo) error {
|
|||||||
|
|
||||||
var fName string
|
var fName string
|
||||||
if c.Opts.Recursive {
|
if c.Opts.Recursive {
|
||||||
fDir := strings.Split(filepath.Dir(fileName), string(os.PathSeparator))[1:]
|
outDir := c.recursiveDir(fileName)
|
||||||
err := os.MkdirAll(filepath.Join(c.Opts.OutDir, filepath.Join(fDir...)), 0755)
|
if err := os.MkdirAll(outDir, 0755); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s: %w", fileName, err)
|
return fmt.Errorf("%s: %w", fileName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fName = filepath.Join(c.Opts.OutDir, filepath.Join(fDir...), fmt.Sprintf("%s.%s", baseNoExt(fileName), ext))
|
fName = filepath.Join(outDir, fmt.Sprintf("%s.%s", baseNoExt(fileName), ext))
|
||||||
} else {
|
} else {
|
||||||
fName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s.%s", baseNoExt(fileName), ext))
|
fName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s.%s", baseNoExt(fileName), ext))
|
||||||
}
|
}
|
||||||
@@ -310,8 +333,10 @@ func (c *Converter) Cover(fileName string, fileInfo os.FileInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Thumbnail extracts thumbnail.
|
// Thumbnail extracts thumbnail.
|
||||||
func (c *Converter) Thumbnail(fileName string, fileInfo os.FileInfo) error {
|
func (c *Converter) Thumbnail(file File) error {
|
||||||
c.CurrFile++
|
c.CurrFile++
|
||||||
|
c.root = file.Root
|
||||||
|
fileName, fileInfo := file.Path, file.Stat
|
||||||
|
|
||||||
cover, err := c.coverImage(fileName, fileInfo)
|
cover, err := c.coverImage(fileName, fileInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -352,13 +377,12 @@ func (c *Converter) Thumbnail(fileName string, fileInfo os.FileInfo) error {
|
|||||||
fURI = "file://" + fileName
|
fURI = "file://" + fileName
|
||||||
|
|
||||||
if c.Opts.Recursive {
|
if c.Opts.Recursive {
|
||||||
fDir := strings.Split(filepath.Dir(fileName), string(os.PathSeparator))[1:]
|
outDir := c.recursiveDir(fileName)
|
||||||
err := os.MkdirAll(filepath.Join(c.Opts.OutDir, filepath.Join(fDir...)), 0755)
|
if err := os.MkdirAll(outDir, 0755); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s: %w", fileName, err)
|
return fmt.Errorf("%s: %w", fileName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fName = filepath.Join(c.Opts.OutDir, filepath.Join(fDir...), fmt.Sprintf("%x.png", md5.Sum([]byte(fURI))))
|
fName = filepath.Join(outDir, fmt.Sprintf("%x.png", md5.Sum([]byte(fURI))))
|
||||||
} else {
|
} else {
|
||||||
fName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%x.png", md5.Sum([]byte(fURI))))
|
fName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%x.png", md5.Sum([]byte(fURI))))
|
||||||
}
|
}
|
||||||
@@ -496,8 +520,10 @@ func (c *Converter) Preview(fileName string, fileInfo os.FileInfo, width, height
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert converts a comic book.
|
// Convert converts a comic book.
|
||||||
func (c *Converter) Convert(fileName string, fileInfo os.FileInfo) error {
|
func (c *Converter) Convert(file File) error {
|
||||||
c.CurrFile++
|
c.CurrFile++
|
||||||
|
c.root = file.Root
|
||||||
|
fileName, fileInfo := file.Path, file.Stat
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -541,6 +567,8 @@ func (c *Converter) Combine(files []File) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.root = ""
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|||||||
@@ -33,13 +33,12 @@ func (c *Converter) archiveSaveZip(fileName string) error {
|
|||||||
|
|
||||||
var zipName string
|
var zipName string
|
||||||
if c.Opts.Recursive {
|
if c.Opts.Recursive {
|
||||||
fDir := strings.Split(filepath.Dir(fileName), string(os.PathSeparator))[1:]
|
outDir := c.recursiveDir(fileName)
|
||||||
err := os.MkdirAll(filepath.Join(c.Opts.OutDir, filepath.Join(fDir...)), 0755)
|
if err := os.MkdirAll(outDir, 0755); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("archiveSaveZip: %w", err)
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
zipName = filepath.Join(c.Opts.OutDir, filepath.Join(fDir...), fmt.Sprintf("%s%s.cbz", baseNoExt(fileName), c.Opts.Suffix))
|
zipName = filepath.Join(outDir, fmt.Sprintf("%s%s.cbz", baseNoExt(fileName), c.Opts.Suffix))
|
||||||
} else {
|
} else {
|
||||||
zipName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s%s.cbz", baseNoExt(fileName), c.Opts.Suffix))
|
zipName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s%s.cbz", baseNoExt(fileName), c.Opts.Suffix))
|
||||||
}
|
}
|
||||||
@@ -108,13 +107,12 @@ func (c *Converter) archiveSaveTar(fileName string) error {
|
|||||||
|
|
||||||
var tarName string
|
var tarName string
|
||||||
if c.Opts.Recursive {
|
if c.Opts.Recursive {
|
||||||
fDir := strings.Split(filepath.Dir(fileName), string(os.PathSeparator))[1:]
|
outDir := c.recursiveDir(fileName)
|
||||||
err := os.MkdirAll(filepath.Join(c.Opts.OutDir, filepath.Join(fDir...)), 0755)
|
if err := os.MkdirAll(outDir, 0755); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("archiveSaveTar: %w", err)
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tarName = filepath.Join(c.Opts.OutDir, filepath.Join(fDir...), fmt.Sprintf("%s%s.cbt", baseNoExt(fileName), c.Opts.Suffix))
|
tarName = filepath.Join(outDir, fmt.Sprintf("%s%s.cbt", baseNoExt(fileName), c.Opts.Suffix))
|
||||||
} else {
|
} else {
|
||||||
tarName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s%s.cbt", baseNoExt(fileName), c.Opts.Suffix))
|
tarName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s%s.cbt", baseNoExt(fileName), c.Opts.Suffix))
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-3
@@ -29,7 +29,7 @@ func TestConvert(t *testing.T) {
|
|||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
conv.Opts.Suffix = fmt.Sprintf("_%s%s", format, filepath.Ext(file.Path))
|
conv.Opts.Suffix = fmt.Sprintf("_%s%s", format, filepath.Ext(file.Path))
|
||||||
|
|
||||||
err = conv.Convert(file.Path, file.Stat)
|
err = conv.Convert(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("format %s: file %s: %v", format, file.Name, err)
|
t.Errorf("format %s: file %s: %v", format, file.Name, err)
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ func TestCover(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
err = conv.Cover(file.Path, file.Stat)
|
err = conv.Cover(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ func TestThumbnail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
err = conv.Thumbnail(file.Path, file.Stat)
|
err = conv.Thumbnail(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -99,3 +99,56 @@ func TestThumbnail(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRecursive(t *testing.T) {
|
||||||
|
inDir, err := os.MkdirTemp(os.TempDir(), "cbc-in")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(inDir)
|
||||||
|
|
||||||
|
sub := filepath.Join(inDir, "chapter1")
|
||||||
|
if err := os.MkdirAll(sub, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := os.ReadFile("testdata/test.cbz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(sub, "test.cbz"), src, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outDir, err := os.MkdirTemp(os.TempDir(), "cbc-out")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(outDir)
|
||||||
|
|
||||||
|
opts := NewOptions()
|
||||||
|
opts.OutDir = outDir
|
||||||
|
opts.Recursive = true
|
||||||
|
|
||||||
|
conv := New(opts)
|
||||||
|
|
||||||
|
files, err := conv.Files([]string{inDir})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(files) != 1 {
|
||||||
|
t.Fatalf("expected 1 file, got %d", len(files))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if err := conv.Convert(file); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// output must mirror the input subtree relative to the input root, not the absolute path
|
||||||
|
want := filepath.Join(outDir, "chapter1", "test.cbz")
|
||||||
|
if _, err := os.Stat(want); err != nil {
|
||||||
|
t.Errorf("expected output relative to input root at %s: %v", want, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1024,7 +1024,7 @@ func onThumbnail(ih iup.Ihandle) int {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Thumbnail(file.Path, file.Stat); err != nil {
|
if err := c.Thumbnail(file); err != nil {
|
||||||
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|
||||||
@@ -1067,7 +1067,7 @@ func onCover(ih iup.Ihandle) int {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.Cover(file.Path, file.Stat); err != nil {
|
if err := c.Cover(file); err != nil {
|
||||||
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|
||||||
@@ -1131,7 +1131,7 @@ func onConvert(ih iup.Ihandle) int {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if err := c.Convert(file.Path, file.Stat); err != nil {
|
if err := c.Convert(file); err != nil {
|
||||||
convertErr(err)
|
convertErr(err)
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
break
|
break
|
||||||
|
|||||||
+25
-12
@@ -68,9 +68,9 @@ func main() {
|
|||||||
if _, err := os.Stat(opts.OutDir); err != nil {
|
if _, err := os.Stat(opts.OutDir); err != nil {
|
||||||
if err := os.MkdirAll(opts.OutDir, 0775); err != nil {
|
if err := os.MkdirAll(opts.OutDir, 0775); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
files, err := conv.Files(args)
|
files, err := conv.Files(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -142,14 +142,14 @@ func main() {
|
|||||||
|
|
||||||
continue
|
continue
|
||||||
case opts.Cover:
|
case opts.Cover:
|
||||||
if err := conv.Cover(file.Path, file.Stat); err != nil {
|
if err := conv.Cover(file); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
case opts.Thumbnail:
|
case opts.Thumbnail:
|
||||||
if err = conv.Thumbnail(file.Path, file.Stat); err != nil {
|
if err = conv.Thumbnail(file); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conv.Convert(file.Path, file.Stat); err != nil {
|
if err := conv.Convert(file); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@@ -279,27 +279,27 @@ func parseFlags() (cbconvert.Options, []string) {
|
|||||||
|
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
case "convert":
|
case "convert":
|
||||||
_ = convert.Parse(os.Args[2:])
|
operands := parseArgs(convert, os.Args[2:])
|
||||||
if !pipe {
|
if !pipe {
|
||||||
args = convert.Args()
|
args = operands
|
||||||
}
|
}
|
||||||
case "cover":
|
case "cover":
|
||||||
opts.Cover = true
|
opts.Cover = true
|
||||||
_ = cover.Parse(os.Args[2:])
|
operands := parseArgs(cover, os.Args[2:])
|
||||||
if !pipe {
|
if !pipe {
|
||||||
args = cover.Args()
|
args = operands
|
||||||
}
|
}
|
||||||
case "thumbnail":
|
case "thumbnail":
|
||||||
opts.Thumbnail = true
|
opts.Thumbnail = true
|
||||||
_ = thumbnail.Parse(os.Args[2:])
|
operands := parseArgs(thumbnail, os.Args[2:])
|
||||||
if !pipe {
|
if !pipe {
|
||||||
args = thumbnail.Args()
|
args = operands
|
||||||
}
|
}
|
||||||
case "meta":
|
case "meta":
|
||||||
opts.Meta = true
|
opts.Meta = true
|
||||||
_ = meta.Parse(os.Args[2:])
|
operands := parseArgs(meta, os.Args[2:])
|
||||||
if !pipe {
|
if !pipe {
|
||||||
args = meta.Args()
|
args = operands
|
||||||
}
|
}
|
||||||
case "version":
|
case "version":
|
||||||
opts.Version = true
|
opts.Version = true
|
||||||
@@ -314,6 +314,19 @@ func parseFlags() (cbconvert.Options, []string) {
|
|||||||
return opts, args
|
return opts, args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseArgs parses flags interspersed with file/dir operands.
|
||||||
|
func parseArgs(fs *flag.FlagSet, args []string) []string {
|
||||||
|
var operands []string
|
||||||
|
|
||||||
|
_ = fs.Parse(args)
|
||||||
|
for fs.NArg() > 0 {
|
||||||
|
operands = append(operands, fs.Arg(0))
|
||||||
|
_ = fs.Parse(fs.Args()[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return operands
|
||||||
|
}
|
||||||
|
|
||||||
// piped checks if we have piped stdin.
|
// piped checks if we have piped stdin.
|
||||||
func piped() bool {
|
func piped() bool {
|
||||||
f, err := os.Stdin.Stat()
|
f, err := os.Stdin.Stat()
|
||||||
|
|||||||
Reference in New Issue
Block a user