diff --git a/README.md b/README.md index 20a406b..eaa3921 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ It can convert comics to different formats to fit your various devices. ### Features * reads RAR, ZIP, 7Z, CBR, CBZ, CB7, CBT, PDF, EPUB, and plain directory -* always saves processed comics in CBZ (ZIP) archive format +* saves processed comics in CBZ (ZIP) archive format or CBT (TAR) * images can be converted to JPEG, PNG, TIFF, WEBP, AVIF, or 4-Bit BMP (16 colors) file format * rotate, flip, adjust brightness/contrast, adjust levels (Photoshop-like) or grayscale images * resize algorithms (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos) @@ -32,7 +32,7 @@ Copy cbconvert cli binary to your PATH and create file ~/.local/share/thumbnaile [Thumbnailer Entry] TryExec=cbconvert Exec=cbconvert thumbnail --quiet --width %s --outfile %o %i -MimeType=application/pdf;application/x-pdf;image/pdf;application/x-cbz;application/x-cbr;application/x-cb7;application/x-cbt;application/epub+zip;application/vnd.comicbook-rar;application/vnd.comicbook+zip; +MimeType=application/pdf;application/x-cb7;application/x-cbt;application/epub+zip;application/vnd.comicbook-rar;application/vnd.comicbook+zip; ``` This is what it looks like in the PCManFM file manager: @@ -59,6 +59,8 @@ This is what it looks like in the PCManFM file manager:             Best fit for required width and height (default "false")         --format             Image format, valid values are jpeg, png, tiff, bmp, webp, avif (default "jpeg") + --archive + Archive format, valid values are zip, tar (default "zip")         --quality             Image quality (default "75")         --lossless @@ -156,9 +158,9 @@ This is what it looks like in the PCManFM file manager: --cover Print cover name (default "false") --comment - Print comment (default "false") + Print zip comment (default "false") --comment-body - Set comment (default "") + Set zip comment (default "") ``` diff --git a/cbconvert.go b/cbconvert.go index 7c6d9e2..a76ef47 100644 --- a/cbconvert.go +++ b/cbconvert.go @@ -1,6 +1,7 @@ package cbconvert import ( + "archive/tar" "archive/zip" "bytes" "context" @@ -68,6 +69,8 @@ var filters = map[int]imaging.ResampleFilter{ type Options struct { // Image format, valid values are jpeg, png, tiff, bmp, webp, avif Format string + // Archive format, valid values are zip, tar + Archive string // JPEG image quality Quality int // Lossless compression (avif) @@ -701,6 +704,17 @@ func (c *Convertor) imEncode(i image.Image, fileName string) error { // archiveSave saves workdir to CBZ archive. func (c *Convertor) archiveSave(fileName string) error { + if c.Opts.Archive == "zip" { + return c.archiveSaveZip(fileName) + } else if c.Opts.Archive == "tar" { + return c.archiveSaveTar(fileName) + } + + return nil +} + +// archiveSaveZip saves workdir to CBZ archive. +func (c *Convertor) archiveSaveZip(fileName string) error { if c.OnCompress != nil { c.OnCompress() } @@ -709,50 +723,108 @@ func (c *Convertor) archiveSave(fileName string) error { zipFile, err := os.Create(zipName) if err != nil { - return fmt.Errorf("archiveSave: %w", err) + return fmt.Errorf("archiveSaveZip: %w", err) } z := zip.NewWriter(zipFile) files, err := os.ReadDir(c.Workdir) if err != nil { - return fmt.Errorf("archiveSave: %w", err) + return fmt.Errorf("archiveSaveZip: %w", err) } for _, file := range files { r, err := os.ReadFile(filepath.Join(c.Workdir, file.Name())) if err != nil { - return fmt.Errorf("archiveSave: %w", err) + return fmt.Errorf("archiveSaveZip: %w", err) } info, err := file.Info() if err != nil { - return fmt.Errorf("archiveSave: %w", err) + return fmt.Errorf("archiveSaveZip: %w", err) } zipInfo, err := zip.FileInfoHeader(info) if err != nil { - return fmt.Errorf("archiveSave: %w", err) + return fmt.Errorf("archiveSaveZip: %w", err) } zipInfo.Method = zip.Deflate w, err := z.CreateHeader(zipInfo) if err != nil { - return fmt.Errorf("archiveSave: %w", err) + return fmt.Errorf("archiveSaveZip: %w", err) } _, err = w.Write(r) if err != nil { - return fmt.Errorf("archiveSave: %w", err) + return fmt.Errorf("archiveSaveZip: %w", err) } } if err = z.Close(); err != nil { - return fmt.Errorf("archiveSave: %w", err) + return fmt.Errorf("archiveSaveZip: %w", err) } if err = zipFile.Close(); err != nil { - return fmt.Errorf("archiveSave: %w", err) + return fmt.Errorf("archiveSaveZip: %w", err) + } + + return os.RemoveAll(c.Workdir) +} + +// archiveSaveTar saves workdir to CBT archive. +func (c *Convertor) archiveSaveTar(fileName string) error { + if c.OnCompress != nil { + c.OnCompress() + } + + tarName := filepath.Join(c.Opts.Outdir, fmt.Sprintf("%s%s.cbt", c.baseNoExt(fileName), c.Opts.Suffix)) + + tarFile, err := os.Create(tarName) + if err != nil { + return fmt.Errorf("archiveSaveTar: %w", err) + } + + tw := tar.NewWriter(tarFile) + + files, err := os.ReadDir(c.Workdir) + if err != nil { + return fmt.Errorf("archiveSaveTar: %w", err) + } + + for _, file := range files { + r, err := os.ReadFile(filepath.Join(c.Workdir, file.Name())) + if err != nil { + return fmt.Errorf("archiveSaveTar: %w", err) + } + + info, err := file.Info() + if err != nil { + return fmt.Errorf("archiveSaveTar: %w", err) + } + + header, err := tar.FileInfoHeader(info, info.Name()) + if err != nil { + return fmt.Errorf("archiveSaveTar: %w", err) + } + + err = tw.WriteHeader(header) + if err != nil { + return fmt.Errorf("archiveSaveTar: %w", err) + } + + _, err = tw.Write(r) + if err != nil { + return fmt.Errorf("archiveSaveTar: %w", err) + } + } + + if err = tw.Close(); err != nil { + return fmt.Errorf("archiveSaveTar: %w", err) + } + + if err = tarFile.Close(); err != nil { + return fmt.Errorf("archiveSaveTar: %w", err) } return os.RemoveAll(c.Workdir) diff --git a/cmd/cbconvert/main.go b/cmd/cbconvert/main.go index 007428f..8f41f74 100644 --- a/cmd/cbconvert/main.go +++ b/cmd/cbconvert/main.go @@ -135,6 +135,7 @@ func parseFlags() (cbconvert.Options, []string) { convert.IntVar(&opts.Height, "height", 0, "Image height") convert.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height") convert.StringVar(&opts.Format, "format", "jpeg", "Image format, valid values are jpeg, png, tiff, bmp, webp, avif") + convert.StringVar(&opts.Archive, "archive", "zip", "Archive format, valid values are zip, tar") convert.IntVar(&opts.Quality, "quality", 75, "Image quality") convert.BoolVar(&opts.Lossless, "lossless", false, "Lossless compression (avif)") convert.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos") @@ -186,8 +187,8 @@ func parseFlags() (cbconvert.Options, []string) { meta := flag.NewFlagSet("meta", flag.ExitOnError) meta.SortFlags = false meta.BoolVar(&opts.Cover, "cover", false, "Print cover name") - meta.BoolVar(&opts.Comment, "comment", false, "Print comment") - meta.StringVar(&opts.CommentBody, "comment-body", "", "Set comment") + meta.BoolVar(&opts.Comment, "comment", false, "Print zip comment") + meta.StringVar(&opts.CommentBody, "comment-body", "", "Set zip comment") convert.Usage = func() { _, _ = fmt.Fprintf(os.Stderr, "Usage: %s [] [file1 dir1 ... fileOrDirN]\n\n", filepath.Base(os.Args[0]))