22 Commits

Author SHA1 Message Date
Milan Nikolic 28ef6910e4 new release 2015-11-05 07:51:47 +01:00
Milan Nikolic 35b8bc340f man page info 2015-11-05 07:46:27 +01:00
Milan Nikolic a5a45306c6 update version 2015-11-05 07:38:48 +01:00
Milan Nikolic b063367110 add progressbar for cover 2015-11-05 07:06:03 +01:00
Milan Nikolic 7831e487e6 add fit for cover and thumbnail 2015-11-05 06:41:09 +01:00
Milan Nikolic ce32d48469 Update README.md 2015-11-05 06:16:39 +01:00
Milan Nikolic 4d1e0a44d2 add brightness and contrast options 2015-11-05 06:11:40 +01:00
Milan Nikolic 574e364954 add support for tiff format 2015-11-05 05:50:11 +01:00
Milan Nikolic 97aae5e0c7 best fit option 2015-11-05 05:06:45 +01:00
Milan Nikolic bbcaf0f9dd add encodeImage and encodeImageMagick 2015-11-05 04:53:35 +01:00
Milan Nikolic d70de9f4db add options to rotate and flip images 2015-11-04 14:20:37 +01:00
Milan Nikolic d7be2f4d5e add kingpin commands 2015-11-04 12:58:26 +01:00
Milan Nikolic 82e8a6cbac add option to remove non-image files from archive 2015-11-04 00:28:18 +01:00
Milan Nikolic 85818eec5f add grayscale option 2015-11-03 22:13:21 +01:00
Milan Nikolic 0c6b9fcaa8 use imaging instead of resize 2015-11-03 21:39:30 +01:00
Milan Nikolic 55b53efcca rename interp. to resize 2015-11-03 20:13:25 +01:00
Milan Nikolic 4abf99a644 add gif support 2015-11-03 19:47:24 +01:00
Milan Nikolic ca4f13ebfe Update README.md 2015-11-03 14:44:42 +01:00
Milan Nikolic a4c7b87dc0 add tiff and webp support 2015-11-03 13:37:02 +01:00
Milan Nikolic dd4c3a2dbf Update README.md 2015-11-02 07:58:35 +01:00
Milan Nikolic 8e3f7962d1 linux static build 2015-11-02 07:55:29 +01:00
Milan Nikolic 1c3421028b Update README.md 2015-11-02 05:53:37 +01:00
3 changed files with 392 additions and 151 deletions
+76 -28
View File
@@ -4,52 +4,92 @@ CBconvert
Introduction
------------
CBconvert is a [Comic Book](http://en.wikipedia.org/wiki/Comic_Book_Archive_file) convert tool.
CBconvert is a [Comic Book](http://en.wikipedia.org/wiki/Comic_Book_Archive_file) converter.
It allows you to convert individual comics or bulk convert comics to different formats to fit your various devices.
Features
--------
- reads RAR, ZIP, 7Z, GZ, BZ2, CBR, CBZ, CB7, CBT, PDF, EPUB, XPS and plain directory
- always saves processed comic in CBZ (ZIP) format
- images can be converted to JPEG, PNG or 4-Bit BMP (16 colors) format
- choose resize algorithm (NearestNeighbor, Bilinear, Bicubic, MitchellNetravali, Lanczos2/3)
- always saves processed comic in CBZ (ZIP) archive format
- images can be converted to JPEG, PNG, GIF, TIFF or 4-Bit BMP (16 colors) file format
- choose resize algorithm (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos)
- rotate, flip, adjust brightness/contrast or grayscale images
- export covers from comics
- create thumbnails from covers by [freedesktop](http://www.freedesktop.org/wiki/) specification
Download
--------
- [Windows static build](https://github.com/gen2brain/cbconvert/releases/download/0.2.0/cbconvert-0.2.0.zip)
- [Linux 64bit build](https://github.com/gen2brain/cbconvert/releases/download/0.2.0/cbconvert-0.2.0.tar.gz)
- [Windows binary](https://github.com/gen2brain/cbconvert/releases/download/0.4.0/cbconvert-0.4.0.zip)
- [Linux 64bit binary](https://github.com/gen2brain/cbconvert/releases/download/0.4.0/cbconvert-0.4.0.tar.gz)
- [Linux 64bit static binary](https://github.com/gen2brain/cbconvert/releases/download/0.4.0/cbconvert-0.4.0-static.tar.gz)
Using
-----
usage: cbconvert [<flags>] <args>...
usage: cbconvert [<flags>] <command> [<args> ...]
Comic Book convert tool.
Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
--version Show application version.
-p, --png encode images to PNG instead of JPEG
-b, --bmp encode images to 4-Bit BMP instead of JPEG
-w, --width=0 image width
-h, --height=0 image height
-q, --quality=75 JPEG image quality
-n, --norgb do not convert images with RGB colorspace
-i, --interpolation=1 0=NearestNeighbor, 1=Bilinear, 2=Bicubic, 3=MitchellNetravali, 4=Lanczos2, 5=Lanczos3
-s, --suffix=SUFFIX add suffix to file basename
-c, --cover extract cover
-t, --thumbnail extract cover thumbnail (freedesktop spec.)
-o, --outdir="." output directory
-m, --size=0 process only files larger then size (in MB)
-r, --recursive process subdirectories recursively
-Q, --quiet hide console output
--outdir="." Output directory
--size=0 Process only files larger then size (in MB)
--recursive Process subdirectories recursively
--quiet Hide console output
Args:
<args> filename or directory
Commands:
help [<command>...]
Show help.
convert [<flags>] <args>...
Convert archive or document (default command)
--width=0 Image width
--height=0 Image height
--fit Best fit for required width and height
--quality=75 JPEG image quality
--filter=2 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
--png Encode images to PNG instead of JPEG
--bmp Encode images to 4-Bit BMP (16 colors) instead of JPEG
--gif Encode images to GIF instead of JPEG
--tiff Encode images to TIFF instead of JPEG
--rgb Convert images that have RGB colorspace (use --no-rgb if you only want to convert grayscale images)
--nonimage Leave non image files in archive (use --no-nonimage to remove non image files from archive)
--grayscale Convert images to grayscale (monochromatic)
--rotate=0 Rotate images, valid values are 0, 90, 180, 270
--flip="none" Flip images, valid values are none, horizontal, vertical
--brightness=0 Adjust brightness of the images, must be in range (-100, 100)
--contrast=0 Adjust contrast of the images, must be in range (-100, 100)
--suffix=SUFFIX Add suffix to file basename
cover [<flags>] <args>...
Extract cover
--width=0 Image width
--height=0 Image height
--fit Best fit for required width and height
--quality=75 JPEG image quality
--filter=2 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
thumbnail [<flags>] <args>...
Extract cover thumbnail (freedesktop spec.)
--width=0 Image width
--height=0 Image height
--fit Best fit for required width and height
--filter=2 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
[man page](https://en.wikipedia.org/wiki/Man_page) is also available:
cbconvert --help-man | man /dev/stdin
Examples
--------
@@ -58,15 +98,19 @@ Rescale images to 1200px for all supported files found in directory with size la
cbconvert --recursive --width 1200 --size 60 /media/comics/Thorgal/
Convert all images in archive to 4bit BMP image and save result in ~/comics directory:
Convert all images in pdf to 4bit BMP image and save result in ~/comics directory:
cbconvert --bmp --outdir ~/comics /media/comics/Garfield/Garfield_01.cbz
cbconvert --bmp --outdir ~/comics /media/comics/Garfield/Garfield_01.pdf
[BMP](http://en.wikipedia.org/wiki/BMP_file_format) format is uncompressed, for black&white pages very good choice. Archive size can be smaller 2-3x and file will be readable by comic readers.
[BMP](http://en.wikipedia.org/wiki/BMP_file_format) format is very good choice for black&white pages. Archive size can be smaller 2-3x and file will be readable by comic readers.
Generate thumbnails by freedesktop specification in ~/.thumbnails/normal directory, Lanczos3 algorithm is used for resizing:
Generate thumbnails by freedesktop specification in ~/.thumbnails/normal directory with width 512:
cbconvert --interpolation=5 --outdir ~/.thumbnails/normal --thumbnail /media/comics/GrooTheWanderer/
cbconvert thumbnail --width 512 --outdir ~/.thumbnails/normal /media/comics/GrooTheWanderer/
Extract covers to ~/covers dir for all supported files found in directory, Lanczos algorithm is used for resizing:
cbconvert cover --outdir ~/covers --filter=7 /media/comics/GrooTheWanderer/
Compile
-------
@@ -79,7 +123,9 @@ Compile latest MuPDF:
git clone git://git.ghostscript.com/mupdf.git && cd mupdf
git submodule update --init --recursive
HAVE_X11=no HAVE_GLFW=no HAVE_GLUT=no WANT_CURL=no make && make install
curl -L https://gist.githubusercontent.com/gen2brain/7869ac4c6db5933f670f/raw/1619394dc957ae10bcd73c713760993466b4bfea/mupdf-openssl-curl.patch | patch -p1
sed -e "1iHAVE_X11 = no" -e "1iWANT_OPENSSL = no" -e "1iWANT_CURL = no" -i Makerules
HAVE_X11=no HAVE_GLFW=no HAVE_GLUT=no WANT_OPENSSL=no WANT_CURL=no HAVE_MUJS=yes HAVE_JSCORE=no HAVE_V8=no make && make install
Compile unarr library:
@@ -94,12 +140,14 @@ Compile unarr library:
Install dependencies:
go get github.com/cheggaaa/pb
go get github.com/disintegration/imaging
go get github.com/gen2brain/go-fitz
go get github.com/gen2brain/go-unarr
go get github.com/gographics/imagick/imagick
go get github.com/hotei/bmp
go get github.com/nfnt/resize
go get github.com/skarademir/naturalsort
go get golang.org/x/image/tiff
go get golang.org/x/image/webp
go get gopkg.in/alecthomas/kingpin.v2
Install go package:
+304 -111
View File
@@ -23,7 +23,7 @@ import (
"fmt"
"image"
"image/color"
_ "image/gif"
"image/gif"
"image/jpeg"
"image/png"
"io"
@@ -40,41 +40,75 @@ import (
"syscall"
"github.com/cheggaaa/pb"
"github.com/disintegration/imaging"
"github.com/gen2brain/go-fitz"
"github.com/gen2brain/go-unarr"
"github.com/gographics/imagick/imagick"
_ "github.com/hotei/bmp"
"github.com/nfnt/resize"
"github.com/skarademir/naturalsort"
"golang.org/x/image/tiff"
_ "golang.org/x/image/webp"
"gopkg.in/alecthomas/kingpin.v2"
)
var (
opts options
workdir string
nfiles int
current int
wg sync.WaitGroup
// Resample filters
const (
NearestNeighbor int = iota // Fastest resampling filter, no antialiasing
Box // Box filter (averaging pixels)
Linear // Bilinear filter, smooth and reasonably fast
MitchellNetravali // А smooth bicubic filter
CatmullRom // A sharp bicubic filter
Gaussian // Blurring filter that uses gaussian function, useful for noise removal
Lanczos // High-quality resampling filter, it's slower than cubic filters
)
// Command line options
type options struct {
var filters = map[int]imaging.ResampleFilter{
NearestNeighbor: imaging.NearestNeighbor,
Box: imaging.Box,
Linear: imaging.Linear,
MitchellNetravali: imaging.MitchellNetravali,
CatmullRom: imaging.CatmullRom,
Gaussian: imaging.Gaussian,
Lanczos: imaging.Lanczos,
}
// Options
type Options struct {
ToPNG bool // encode images to PNG instead of JPEG
ToBMP bool // encode images to 4-Bit BMP instead of JPEG
ToBMP bool // encode images to 4-Bit BMP (16 colors) instead of JPEG
ToGIF bool // encode images to GIF instead of JPEG
ToTIFF bool // encode images to TIFF instead of JPEG
Quality int // JPEG image quality
NoRGB bool // do not convert images with RGB colorspace
Width uint // image width
Height uint // image height
Interpolation int // 0=NearestNeighbor, 1=Bilinear, 2=Bicubic, 3=MitchellNetravali, 4=Lanczos2, 5=Lanczos3
Width int // image width
Height int // image height
Fit bool // Best fit for required width and height
Filter int // 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
RGB bool // convert images that have RGB colorspace
NonImage bool // Leave non image files in archive
Suffix string // add suffix to file basename
Cover bool // extract cover
Thumbnail bool // extract cover thumbnail (freedesktop spec.)
Outdir string // output directory
Grayscale bool // convert images to grayscale (monochromatic)
Rotate int // Rotate images, valid values are 0, 90, 180, 270
Flip string // Flip images, valid values are none, horizontal, vertical
Brightness float64 // Adjust brightness of the images, must be in range (-100, 100)
Contrast float64 // Adjust contrast of the images, must be in range (-100, 100)
Recursive bool // process subdirectories recursively
Size int64 // process only files larger then size (in MB)
Quiet bool // hide console output
}
// Globals
var (
opts Options
workdir string
nfiles int
current int
bar *pb.ProgressBar
wg sync.WaitGroup
)
// Command line arguments
var arguments []string
@@ -90,6 +124,10 @@ func convertImage(img image.Image, index int, pathName string) {
ext = "png"
} else if opts.ToBMP {
ext = "bmp"
} else if opts.ToGIF {
ext = "gif"
} else if opts.ToTIFF {
ext = "tiff"
}
var filename string
@@ -99,61 +137,77 @@ func convertImage(img image.Image, index int, pathName string) {
filename = filepath.Join(workdir, fmt.Sprintf("%03d.%s", index, ext))
}
var i image.Image
if opts.Width > 0 || opts.Height > 0 {
i = resize.Resize(opts.Width, opts.Height, img,
resize.InterpolationFunction(opts.Interpolation))
} else {
i = img
}
if opts.ToPNG {
// convert image to PNG
f, err := os.Create(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "Error Create: %v\n", err.Error())
if opts.Grayscale {
encodeImageMagick(img, filename)
} else {
encodeImage(img, filename)
}
defer f.Close()
png.Encode(f, i)
} else if opts.ToBMP {
// convert image to 4-Bit - 16 colors BMP
imagick.Initialize()
mw := imagick.NewMagickWand()
defer mw.Destroy()
b := new(bytes.Buffer)
jpeg.Encode(b, i, &jpeg.Options{jpeg.DefaultQuality})
err := mw.ReadImageBlob(b.Bytes())
if err != nil {
fmt.Fprintf(os.Stderr, "Error ReadImageBlob: %v\n", err.Error())
}
w := imagick.NewPixelWand()
w.SetColor("black")
defer w.Destroy()
mw.SetImageBackgroundColor(w)
mw.SetImageAlphaChannel(imagick.ALPHA_CHANNEL_REMOVE)
mw.SetImageAlphaChannel(imagick.ALPHA_CHANNEL_DEACTIVATE)
mw.SetImageMatte(false)
mw.SetImageCompression(imagick.COMPRESSION_NO)
mw.QuantizeImage(16, imagick.COLORSPACE_SRGB, 8, true, true)
mw.WriteImage(fmt.Sprintf("BMP3:%s", filename))
// convert image to 4-Bit BMP (16 colors)
encodeImageMagick(img, filename)
} else if opts.ToGIF {
// convert image to GIF
encodeImageMagick(img, filename)
} else if opts.ToTIFF {
// convert image to TIFF
encodeImage(img, filename)
} else {
// convert image to JPEG (default)
f, err := os.Create(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "Error Create: %v\n", err.Error())
if opts.Grayscale {
encodeImageMagick(img, filename)
} else {
encodeImage(img, filename)
}
defer f.Close()
jpeg.Encode(f, i, &jpeg.Options{opts.Quality})
}
<-throttle
}
// Transforms image (resize, rotate, flip, brightness, contrast)
func transformImage(img image.Image) image.Image {
var i image.Image = img
if opts.Width > 0 || opts.Height > 0 {
if opts.Fit {
i = imaging.Fit(i, opts.Width, opts.Height, filters[opts.Filter])
} else {
i = imaging.Resize(i, opts.Width, opts.Height, filters[opts.Filter])
}
}
if opts.Rotate > 0 {
switch opts.Rotate {
case 90:
i = imaging.Rotate90(i)
case 180:
i = imaging.Rotate180(i)
case 270:
i = imaging.Rotate270(i)
}
}
if opts.Flip != "none" {
switch opts.Flip {
case "horizontal":
i = imaging.FlipH(i)
case "vertical":
i = imaging.FlipV(i)
}
}
if opts.Brightness != 0 {
i = imaging.AdjustBrightness(i, opts.Brightness)
}
if opts.Contrast != 0 {
i = imaging.AdjustContrast(i, opts.Contrast)
}
return i
}
// Converts PDF/EPUB/XPS document to CBZ
func convertDocument(file string) {
workdir, _ = ioutil.TempDir(os.TempDir(), "cbc")
@@ -166,7 +220,6 @@ func convertDocument(file string) {
npages := doc.Pages()
var bar *pb.ProgressBar
if !opts.Quiet {
bar = pb.New(npages)
bar.ShowTimeLeft = false
@@ -181,7 +234,11 @@ func convertDocument(file string) {
img, err := doc.Image(n)
if err == nil && img != nil {
if err == nil {
img = transformImage(img)
}
if img != nil {
throttle <- 1
wg.Add(1)
@@ -203,7 +260,6 @@ func convertArchive(file string) {
}
defer archive.Close()
var bar *pb.ProgressBar
if !opts.Quiet {
bar = pb.New(ncontents)
bar.ShowTimeLeft = false
@@ -250,20 +306,24 @@ func convertArchive(file string) {
continue
}
if opts.NoRGB && !isGrayScale(img) {
copyFile(bytes.NewReader(buf), filepath.Join(workdir, filepath.Base(pathname)))
i := transformImage(img)
if !opts.RGB && !isGrayScale(i) {
encodeImage(i, filepath.Join(workdir, filepath.Base(pathname)))
continue
}
if img != nil {
if i != nil {
throttle <- 1
wg.Add(1)
go convertImage(img, 0, pathname)
go convertImage(i, 0, pathname)
}
} else {
if opts.NonImage {
copyFile(bytes.NewReader(buf), filepath.Join(workdir, filepath.Base(pathname)))
}
}
}
wg.Wait()
}
@@ -273,7 +333,6 @@ func convertDirectory(path string) {
images := getImages(path)
var bar *pb.ProgressBar
if !opts.Quiet {
bar = pb.New(nfiles)
bar.ShowTimeLeft = false
@@ -298,8 +357,10 @@ func convertDirectory(path string) {
continue
}
if opts.NoRGB && !isGrayScale(i) {
copyFile(f, filepath.Join(workdir, filepath.Base(img)))
i = transformImage(i)
if !opts.RGB && !isGrayScale(i) {
encodeImage(i, filepath.Join(workdir, filepath.Base(img)))
continue
}
@@ -329,7 +390,6 @@ func saveArchive(file string) {
z := zip.NewWriter(zipfile)
files, _ := ioutil.ReadDir(workdir)
var bar *pb.ProgressBar
if !opts.Quiet {
bar = pb.New(len(files))
bar.ShowTimeLeft = false
@@ -358,6 +418,95 @@ func saveArchive(file string) {
z.Close()
}
// Decodes image from reader
func decodeImage(reader io.Reader, filename string) (i image.Image, err error) {
defer func() {
if r := recover(); r != nil {
fmt.Fprintf(os.Stderr, "Recovered in decodeImage %s: %v\n", filename, r)
}
}()
i, _, err = image.Decode(reader)
return i, err
}
// Encode image to file
func encodeImage(i image.Image, filename string) (err error) {
f, err := os.Create(filename)
if err != nil {
return
}
switch filepath.Ext(filename) {
case ".png":
err = png.Encode(f, i)
case ".tif":
case ".tiff":
err = tiff.Encode(f, i, &tiff.Options{tiff.Uncompressed, false})
case ".gif":
err = gif.Encode(f, i, nil)
default:
err = jpeg.Encode(f, i, &jpeg.Options{opts.Quality})
}
f.Close()
return
}
// Encode image to file (ImageMagick)
func encodeImageMagick(i image.Image, filename string) (err error) {
imagick.Initialize()
mw := imagick.NewMagickWand()
defer mw.Destroy()
b := new(bytes.Buffer)
jpeg.Encode(b, i, &jpeg.Options{opts.Quality})
err = mw.ReadImageBlob(b.Bytes())
if err != nil {
fmt.Fprintf(os.Stderr, "Error ReadImageBlob: %v\n", err.Error())
return
}
if opts.Grayscale {
c := mw.GetImageColors()
mw.QuantizeImage(c, imagick.COLORSPACE_GRAY, 8, true, true)
}
switch filepath.Ext(filename) {
case ".png":
mw.SetImageFormat("PNG")
mw.WriteImage(filename)
case ".gif":
mw.SetImageFormat("GIF")
mw.WriteImage(filename)
case ".bmp":
w := imagick.NewPixelWand()
w.SetColor("black")
defer w.Destroy()
cs := mw.GetImageColorspace()
if opts.Grayscale {
cs = imagick.COLORSPACE_GRAY
}
mw.SetImageFormat("BMP3")
mw.SetImageBackgroundColor(w)
mw.SetImageAlphaChannel(imagick.ALPHA_CHANNEL_REMOVE)
mw.SetImageAlphaChannel(imagick.ALPHA_CHANNEL_DEACTIVATE)
mw.SetImageMatte(false)
mw.SetImageCompression(imagick.COMPRESSION_NO)
mw.QuantizeImage(16, cs, 8, true, true)
mw.WriteImage(filename)
default:
mw.SetImageFormat("JPEG")
mw.WriteImage(filename)
}
return
}
// Lists contents of archive
func listArchive(file string) []string {
var contents []string
@@ -511,7 +660,7 @@ func getFiles() []string {
for _, f := range fs {
if isArchive(f.Name()) || isArchive(f.Name()) {
if isSize(f.Size()) {
files = append(files, f.Name())
files = append(files, filepath.Join(path, f.Name()))
}
}
}
@@ -560,6 +709,10 @@ func getImages(path string) []string {
// Returns the filename that is the most likely to be the cover
func getCover(images []string) string {
if len(images) == 0 {
return ""
}
for _, i := range images {
if strings.HasPrefix(i, "cover") || strings.HasPrefix(i, "front") {
return i
@@ -570,7 +723,7 @@ func getCover(images []string) string {
return images[0]
}
// Checks if file is comic
// Checks if file is archive
func isArchive(f string) bool {
var types = []string{".rar", ".zip", ".7z", ".gz",
".bz2", ".cbr", ".cbz", ".cb7", ".cbt"}
@@ -595,8 +748,8 @@ func isDocument(f string) bool {
// Checks if file is image
func isImage(f string) bool {
var types = []string{".jpg", ".jpeg", ".jpe",
".png", ".gif", ".bmp"}
var types = []string{".jpg", ".jpeg", ".jpe", ".png",
".gif", ".bmp", ".tiff", ".tif", ".webp"}
for _, t := range types {
if strings.ToLower(filepath.Ext(f)) == t {
return true
@@ -624,18 +777,6 @@ func isGrayScale(img image.Image) bool {
return false
}
// Decodes image from reader
func decodeImage(reader io.Reader, filename string) (i image.Image, err error) {
defer func() {
if r := recover(); r != nil {
fmt.Fprintf(os.Stderr, "Recovered in decodeImage %s: %v\n", filename, r)
}
}()
i, _, err = image.Decode(reader)
return i, err
}
// Copies reader to file
func copyFile(reader io.Reader, filename string) error {
os.MkdirAll(filepath.Dir(filename), 0755)
@@ -679,8 +820,11 @@ func extractCover(file string, info os.FileInfo) {
}
if opts.Width > 0 || opts.Height > 0 {
cover = resize.Resize(opts.Width, opts.Height, cover,
resize.InterpolationFunction(opts.Interpolation))
if opts.Fit {
cover = imaging.Fit(cover, opts.Width, opts.Height, filters[opts.Filter])
} else {
cover = imaging.Resize(cover, opts.Width, opts.Height, filters[opts.Filter])
}
}
filename := filepath.Join(opts.Outdir, fmt.Sprintf("%s.jpg", getBasename(file)))
@@ -712,11 +856,13 @@ func extractThumbnail(file string, info os.FileInfo) {
}
if opts.Width > 0 || opts.Height > 0 {
cover = resize.Resize(opts.Width, opts.Height, cover,
resize.InterpolationFunction(opts.Interpolation))
if opts.Fit {
cover = imaging.Fit(cover, opts.Width, opts.Height, filters[opts.Filter])
} else {
cover = resize.Resize(256, 0, cover,
resize.InterpolationFunction(opts.Interpolation))
cover = imaging.Resize(cover, opts.Width, opts.Height, filters[opts.Filter])
}
} else {
cover = imaging.Resize(cover, 256, 0, filters[opts.Filter])
}
imagick.Initialize()
@@ -735,7 +881,7 @@ func extractThumbnail(file string, info os.FileInfo) {
fileuri := "file://" + file
filename := filepath.Join(opts.Outdir, fmt.Sprintf("%x.png", md5.Sum([]byte(fileuri))))
mw.SetImageFormat("png")
mw.SetImageFormat("PNG")
mw.SetImageProperty("Software", "cbconvert")
mw.SetImageProperty("Description", "Thumbnail of "+fileuri)
mw.SetImageProperty("Thumb::URI", fileuri)
@@ -762,26 +908,57 @@ func convertComic(file string, info os.FileInfo) {
// Parses command line flags
func parseFlags() {
opts = options{}
kingpin.Version("CBconvert 0.3.0")
opts = Options{}
kingpin.Version("CBconvert 0.4.0")
kingpin.CommandLine.Help = "Comic Book convert tool."
kingpin.Flag("png", "encode images to PNG instead of JPEG").Short('p').BoolVar(&opts.ToPNG)
kingpin.Flag("bmp", "encode images to 4-Bit BMP instead of JPEG").Short('b').BoolVar(&opts.ToBMP)
kingpin.Flag("width", "image width").Default(strconv.Itoa(0)).Short('w').UintVar(&opts.Width)
kingpin.Flag("height", "image height").Default(strconv.Itoa(0)).Short('h').UintVar(&opts.Height)
kingpin.Flag("quality", "JPEG image quality").Short('q').Default(strconv.Itoa(jpeg.DefaultQuality)).IntVar(&opts.Quality)
kingpin.Flag("norgb", "do not convert images with RGB colorspace").Short('n').BoolVar(&opts.NoRGB)
kingpin.Flag("interpolation", "0=NearestNeighbor, 1=Bilinear, 2=Bicubic, 3=MitchellNetravali, 4=Lanczos2, 5=Lanczos3").Short('i').
Default(strconv.Itoa(int(resize.Bilinear))).IntVar(&opts.Interpolation)
kingpin.Flag("suffix", "add suffix to file basename").Short('s').StringVar(&opts.Suffix)
kingpin.Flag("cover", "extract cover").Short('c').BoolVar(&opts.Cover)
kingpin.Flag("thumbnail", "extract cover thumbnail (freedesktop spec.)").Short('t').BoolVar(&opts.Thumbnail)
kingpin.Flag("outdir", "output directory").Default(".").Short('o').StringVar(&opts.Outdir)
kingpin.Flag("size", "process only files larger then size (in MB)").Short('m').Default(strconv.Itoa(0)).Int64Var(&opts.Size)
kingpin.Flag("recursive", "process subdirectories recursively").Short('r').BoolVar(&opts.Recursive)
kingpin.Flag("quiet", "hide console output").Short('Q').BoolVar(&opts.Quiet)
kingpin.Arg("args", "filename or directory").Required().ExistingFilesOrDirsVar(&arguments)
kingpin.Parse()
kingpin.UsageTemplate(kingpin.CompactUsageTemplate)
kingpin.Flag("outdir", "Output directory").Default(".").StringVar(&opts.Outdir)
kingpin.Flag("size", "Process only files larger then size (in MB)").Default(strconv.Itoa(0)).Int64Var(&opts.Size)
kingpin.Flag("recursive", "Process subdirectories recursively").BoolVar(&opts.Recursive)
kingpin.Flag("quiet", "Hide console output").BoolVar(&opts.Quiet)
convert := kingpin.Command("convert", "Convert archive or document (default command)").Default()
convert.Arg("args", "filename or directory").Required().ExistingFilesOrDirsVar(&arguments)
convert.Flag("width", "Image width").Default(strconv.Itoa(0)).IntVar(&opts.Width)
convert.Flag("height", "Image height").Default(strconv.Itoa(0)).IntVar(&opts.Height)
convert.Flag("fit", "Best fit for required width and height").BoolVar(&opts.Fit)
convert.Flag("quality", "JPEG image quality").Default(strconv.Itoa(jpeg.DefaultQuality)).IntVar(&opts.Quality)
convert.Flag("filter", "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos").Default(strconv.Itoa(Linear)).IntVar(&opts.Filter)
convert.Flag("png", "Encode images to PNG instead of JPEG").BoolVar(&opts.ToPNG)
convert.Flag("bmp", "Encode images to 4-Bit BMP (16 colors) instead of JPEG").BoolVar(&opts.ToBMP)
convert.Flag("gif", "Encode images to GIF instead of JPEG").BoolVar(&opts.ToGIF)
convert.Flag("tiff", "Encode images to TIFF instead of JPEG").BoolVar(&opts.ToTIFF)
convert.Flag("rgb", "Convert images that have RGB colorspace (use --no-rgb if you only want to convert grayscale images)").Default("true").BoolVar(&opts.RGB)
convert.Flag("nonimage", "Leave non image files in archive (use --no-nonimage to remove non image files from archive)").Default("true").BoolVar(&opts.NonImage)
convert.Flag("grayscale", "Convert images to grayscale (monochromatic)").BoolVar(&opts.Grayscale)
convert.Flag("rotate", "Rotate images, valid values are 0, 90, 180, 270").Default(strconv.Itoa(0)).IntVar(&opts.Rotate)
convert.Flag("flip", "Flip images, valid values are none, horizontal, vertical").Default("none").StringVar(&opts.Flip)
convert.Flag("brightness", "Adjust brightness of the images, must be in range (-100, 100)").Default(strconv.Itoa(0)).Float64Var(&opts.Brightness)
convert.Flag("contrast", "Adjust contrast of the images, must be in range (-100, 100)").Default(strconv.Itoa(0)).Float64Var(&opts.Contrast)
convert.Flag("suffix", "Add suffix to file basename").StringVar(&opts.Suffix)
cover := kingpin.Command("cover", "Extract cover")
cover.Arg("args", "filename or directory").Required().ExistingFilesOrDirsVar(&arguments)
cover.Flag("width", "Image width").Default(strconv.Itoa(0)).IntVar(&opts.Width)
cover.Flag("height", "Image height").Default(strconv.Itoa(0)).IntVar(&opts.Height)
cover.Flag("fit", "Best fit for required width and height").BoolVar(&opts.Fit)
cover.Flag("quality", "JPEG image quality").Default(strconv.Itoa(jpeg.DefaultQuality)).IntVar(&opts.Quality)
cover.Flag("filter", "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos").Default(strconv.Itoa(Linear)).IntVar(&opts.Filter)
thumbnail := kingpin.Command("thumbnail", "Extract cover thumbnail (freedesktop spec.)")
thumbnail.Arg("args", "filename or directory").Required().ExistingFilesOrDirsVar(&arguments)
thumbnail.Flag("width", "Image width").Default(strconv.Itoa(0)).IntVar(&opts.Width)
thumbnail.Flag("height", "Image height").Default(strconv.Itoa(0)).IntVar(&opts.Height)
thumbnail.Flag("fit", "Best fit for required width and height").BoolVar(&opts.Fit)
thumbnail.Flag("filter", "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos").Default(strconv.Itoa(Linear)).IntVar(&opts.Filter)
switch kingpin.Parse() {
case "cover":
opts.Cover = true
case "thumbnail":
opts.Thumbnail = true
}
}
func main() {
@@ -791,6 +968,7 @@ func main() {
signal.Notify(c, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM)
go func() {
for _ = range c {
fmt.Fprintf(os.Stderr, "Aborting\n")
os.RemoveAll(workdir)
os.Exit(1)
}
@@ -802,6 +980,15 @@ func main() {
files := getFiles()
nfiles = len(files)
if opts.Cover || opts.Thumbnail {
if !opts.Quiet {
bar = pb.New(nfiles)
bar.ShowTimeLeft = false
bar.Start()
}
}
for n, file := range files {
current = n + 1
@@ -813,9 +1000,15 @@ func main() {
if opts.Cover {
extractCover(file, stat)
if !opts.Quiet {
bar.Increment()
}
continue
} else if opts.Thumbnail {
extractThumbnail(file, stat)
if !opts.Quiet {
bar.Increment()
}
continue
}
+2 -2
View File
@@ -5,8 +5,8 @@ mkdir -p build
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o build/cbconvert
strip build/cbconvert
#CGO_LDFLAGS="-lm -lz -ldl -lltdl -lfreetype -static-libgcc" CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -v -x -o build/cbconvert-static --ldflags '-extldflags "-static"'
#strip build/cbconvert-static
CGO_LDFLAGS="-ldl -lltdl -lfreetype -lm -lz -static-libgcc" CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o build/cbconvert-static --ldflags '-extldflags "-static"'
strip build/cbconvert-static
CGO_LDFLAGS="-L/usr/i686-pc-mingw32/usr/lib" \
CGO_CFLAGS="-I/usr/i686-pc-mingw32/usr/include -Wno-poison-system-directories" \