add overwrite arg

This commit is contained in:
Aurélie Delhaie
2023-07-23 14:04:43 +02:00
parent 10867d2d85
commit ab31e2e3ff

549
main.go
View File

@@ -1,248 +1,301 @@
package main package main
import ( import (
"bufio" "bufio"
"flag" "flag"
"fmt" "fmt"
"image/jpeg" "image/jpeg"
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/adrium/goheif" "github.com/adrium/goheif"
"github.com/mojitaurelie/heic2jpg/exif" "github.com/mojitaurelie/heic2jpg/exif"
) )
type ( type (
filer interface { filer interface {
IsDir() bool IsDir() bool
Name() string Name() string
} }
) )
func usage() { func usage() {
fmt.Printf("Usage: %s [OPTIONS] <file>\n\n", os.Args[0]) fmt.Printf("Usage: %s [OPTIONS] <file>\n\n", os.Args[0])
fmt.Printf("Note: <file> is ignored when -dir is set\n\nOPTIONS:\n") fmt.Printf("Note: <file> is ignored when -dir is set\n\nOPTIONS:\n")
flag.PrintDefaults() flag.PrintDefaults()
} }
func main() { func main() {
go func() { go func() {
if r := recover(); r != nil { if r := recover(); r != nil {
fmt.Fprintln(os.Stderr, "E: ", r) _, err := fmt.Fprintln(os.Stderr, "E: ", r)
os.Exit(-1) if err != nil {
} os.Exit(-255)
}() }
flag.Usage = usage os.Exit(-1)
dir := flag.String("dir", "", "Directory to analyse") }
recursive := flag.Bool("r", false, "Analyzes the directory recursively (Require -dir)") }()
jpegQuality := flag.Int("q", jpeg.DefaultQuality, "Define the jpeg quality") flag.Usage = usage
ignoreErr := flag.Bool("ignore-errors", false, "Continue to convert when the converter encounter an error") dir := flag.String("dir", "", "Directory to analyse")
removeOrig := flag.Bool("remove-original", false, "Remove the heic file after conversion") recursive := flag.Bool("r", false, "Analyzes the directory recursively (Require -dir)")
outPath := flag.String("o", "", "Output directory") jpegQuality := flag.Int("q", jpeg.DefaultQuality, "Define the jpeg quality")
flag.Parse() ignoreErr := flag.Bool("ignore-errors", false, "Continue to convert when the converter encounter an error")
removeOrig := flag.Bool("remove-original", false, "Remove the heic file after conversion")
if *jpegQuality < 1 || *jpegQuality > 100 { overwriteOut := flag.Bool("overwrite", false, "Overwrite file if already exists")
fmt.Println("W: Illegal value for jpeg quality, quality is set to ", jpeg.DefaultQuality) outPath := flag.String("o", "", "Output directory")
*jpegQuality = jpeg.DefaultQuality flag.Parse()
}
if *jpegQuality < 1 || *jpegQuality > 100 {
var files []string fmt.Println("W: Illegal value for jpeg quality, quality is set to ", jpeg.DefaultQuality)
if len(*dir) == 0 { *jpegQuality = jpeg.DefaultQuality
if len(os.Args) == 1 { }
fmt.Fprintln(os.Stderr, "E: Missing file path")
os.Exit(-1) var files []string
} if len(*dir) == 0 {
files = append(files, os.Args[len(os.Args)-1]) if len(os.Args) == 1 {
} else { _, err := fmt.Fprintln(os.Stderr, "E: Missing file path")
var err error if err != nil {
files, err = getHEICFiles(*dir, *recursive) panic(err)
if err != nil { }
fmt.Fprintf(os.Stderr, "E: Failed to analyse directory: %v\n", err) os.Exit(-1)
os.Exit(-1) }
} files = append(files, os.Args[len(os.Args)-1])
} } else {
if len(files) == 0 { fmt.Printf("Analyzing folder %s\n", *dir)
fmt.Println("No heic file found") var err error
return files, err = getHEICFiles(*dir, *recursive)
} if err != nil {
_, err := fmt.Fprintf(os.Stderr, "E: Failed to analyse directory: %v\n", err)
fmt.Printf("%d files found with the extension .heic\n\n", len(files)) if err != nil {
panic(err)
if *removeOrig { }
fmt.Printf("WARNING: all the .heic will be deleted after the convertion\n\n") os.Exit(-1)
} }
}
if askForConfirmation("Do you want to start the conversion?") { if len(files) == 0 {
out := *dir fmt.Println("No heic file found")
overwritePath := false return
if len(*outPath) > 0 { }
out = *outPath
overwritePath = true fmt.Printf("%d files found with the extension .heic\n\n", len(files))
}
start := time.Now() if *removeOrig {
converted := convert(out, files, *ignoreErr, *jpegQuality, overwritePath) fmt.Printf("WARNING: all the .heic will be deleted after the convertion\n\n")
}
if *removeOrig {
for _, file := range converted { if askForConfirmation("Do you want to start the conversion?") {
err := os.Remove(file) out := *dir
if err != nil { rewritePath := false
fmt.Fprintf(os.Stderr, "E: Failed to remove %s: %v\n", file, err) if len(*outPath) > 0 {
} out = *outPath
} rewritePath = true
} }
start := time.Now()
fmt.Printf("%d file(s) converted in %v\n", len(converted), time.Since(start)) converted := convert(out, files, *ignoreErr, *jpegQuality, rewritePath, *overwriteOut)
}
} if *removeOrig {
for _, file := range converted {
func getHEICFiles(dir string, rec bool) ([]string, error) { err := os.Remove(file)
var res []string if err != nil {
i, err := os.Stat(dir) _, err := fmt.Fprintf(os.Stderr, "E: Failed to remove %s: %v\n", file, err)
if err != nil { if err != nil {
return nil, err panic(err)
} }
if !i.IsDir() { }
return nil, fmt.Errorf("%s is not a directory", dir) }
} }
if rec {
err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { fmt.Printf("%d file(s) converted in %v\n", len(converted), time.Since(start))
if isHEIC(info) { }
res = append(res, path) }
}
return nil func getHEICFiles(dir string, rec bool) ([]string, error) {
}) var res []string
if err != nil { i, err := os.Stat(dir)
return nil, err if err != nil {
} return nil, err
return res, nil }
} if !i.IsDir() {
entries, err := os.ReadDir(dir) return nil, fmt.Errorf("%s is not a directory", dir)
if err != nil { }
return nil, err if rec {
} err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
for _, info := range entries { if isHEIC(info) {
if isHEIC(info) { res = append(res, path)
res = append(res, filepath.Join(dir, info.Name())) }
} return nil
} })
return res, nil if err != nil {
} return nil, err
}
func isHEIC(f filer) bool { return res, nil
if f == nil { }
return false entries, err := os.ReadDir(dir)
} if err != nil {
return !f.IsDir() && strings.HasSuffix(f.Name(), ".heic") return nil, err
} }
for _, info := range entries {
func convert(out string, files []string, ignoreErr bool, jpegQuality int, overwritePath bool) []string { if isHEIC(info) {
var success []string res = append(res, filepath.Join(dir, info.Name()))
wg := new(sync.WaitGroup) }
chunk := chunkWork(files) }
for _, files := range chunk { return res, nil
wg.Add(1) }
go func(files []string, wg *sync.WaitGroup) {
for _, file := range files { func isHEIC(f filer) bool {
fi, err := os.Open(file) if f == nil {
if err != nil { return false
fmt.Fprintf(os.Stderr, "E: Failed to open %s: %v\n", file, err) }
if ignoreErr { return !f.IsDir() && strings.HasSuffix(f.Name(), ".heic")
continue }
}
os.Exit(-1) func convert(out string, files []string, ignoreErr bool, jpegQuality int, rewritePath, overwrite bool) []string {
} var success []string
wg := new(sync.WaitGroup)
ex, err := goheif.ExtractExif(fi) chunk := chunkWork(files)
if err != nil { for _, files := range chunk {
fmt.Printf("W: no EXIF from %s: %v\n", file, err) wg.Add(1)
} go func(files []string, wg *sync.WaitGroup) {
for _, file := range files {
img, err := goheif.Decode(fi) // Generate new path
if err != nil { fout := strings.TrimSuffix(file, ".heic") + ".jpg"
fi.Close() if rewritePath {
fmt.Fprintf(os.Stderr, "E: Failed to decode %s: %v\n", file, err) fout = filepath.Join(out, filepath.Base(fout))
if ignoreErr { }
continue
} // Ignore if file exists and overwirte arg not true
os.Exit(-1) if !overwrite {
} if _, err := os.Stat(fout); err == nil {
continue
fout := strings.TrimSuffix(file, ".heic") + ".jpg" }
if overwritePath { }
fout = filepath.Join(out, filepath.Base(fout))
} // Open source file
fi, err := os.Open(file)
fo, err := os.OpenFile(fout, os.O_RDWR|os.O_CREATE, 0644) if err != nil {
if err != nil { _, err := fmt.Fprintf(os.Stderr, "E: Failed to open %s: %v\n", file, err)
fi.Close() if err != nil {
fmt.Fprintf(os.Stderr, "E: Failed to create output file %s: %v\n", fout, err) panic(err)
if ignoreErr { }
continue if ignoreErr {
} continue
os.Exit(-1) }
} os.Exit(-1)
}
w, _ := exif.NewWriterExif(fo, ex)
err = jpeg.Encode(w, img, &jpeg.Options{ // Extract exif
Quality: jpegQuality, ex, err := goheif.ExtractExif(fi)
}) if err != nil {
if err != nil { fmt.Printf("W: no EXIF from %s: %v\n", file, err)
fi.Close() }
fo.Close()
fmt.Fprintf(os.Stderr, "E: Failed to encode %s: %v\n", fout, err) // Parse jpg to bitmap
if ignoreErr { img, err := goheif.Decode(fi)
continue if err != nil {
} err := fi.Close()
os.Exit(-1) if err != nil {
} panic(err)
fi.Close() }
fo.Close() _, err = fmt.Fprintf(os.Stderr, "E: Failed to decode %s: %v\n", file, err)
success = append(success, file) if err != nil {
} panic(err)
wg.Done() }
}(files, wg) if ignoreErr {
} continue
wg.Wait() }
return success os.Exit(-1)
} }
func chunkWork(files []string) [][]string { // Open out file
var divided [][]string fo, err := os.OpenFile(fout, os.O_RDWR|os.O_CREATE, 0644)
chunkSize := (len(files) + runtime.NumCPU() - 1) / runtime.NumCPU() if err != nil {
for i := 0; i < len(files); i += chunkSize { err := fi.Close()
end := i + chunkSize if err != nil {
panic(err)
if end > len(files) { }
end = len(files) _, err = fmt.Fprintf(os.Stderr, "E: Failed to create output file %s: %v\n", fout, err)
} if err != nil {
panic(err)
divided = append(divided, files[i:end]) }
} if ignoreErr {
return divided continue
} }
os.Exit(-1)
func askForConfirmation(s string) bool { }
reader := bufio.NewReader(os.Stdin)
// Convert and write to jpg file
for { w, _ := exif.NewWriterExif(fo, ex)
fmt.Printf("%s [y/n]: ", s) err = jpeg.Encode(w, img, &jpeg.Options{
Quality: jpegQuality,
response, err := reader.ReadString('\n') })
if err != nil { if err != nil {
panic(err) err := fi.Close()
} if err != nil {
panic(err)
response = strings.ToLower(strings.TrimSpace(response)) }
err = fo.Close()
if response == "y" || response == "yes" { if err != nil {
return true panic(err)
} else if response == "n" || response == "no" { }
return false _, err = fmt.Fprintf(os.Stderr, "E: Failed to encode %s: %v\n", fout, err)
} if err != nil {
} panic(err)
} }
if ignoreErr {
continue
}
os.Exit(-1)
}
err = fi.Close()
if err != nil {
panic(err)
}
err = fo.Close()
if err != nil {
panic(err)
}
success = append(success, file)
}
wg.Done()
}(files, wg)
}
wg.Wait()
return success
}
func chunkWork(files []string) [][]string {
var divided [][]string
chunkSize := (len(files) + runtime.NumCPU() - 1) / runtime.NumCPU()
for i := 0; i < len(files); i += chunkSize {
end := i + chunkSize
if end > len(files) {
end = len(files)
}
divided = append(divided, files[i:end])
}
return divided
}
func askForConfirmation(s string) bool {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("%s [y/N]: ", s)
response, err := reader.ReadString('\n')
if err != nil {
panic(err)
}
response = strings.ToLower(strings.TrimSpace(response))
if response == "y" || response == "yes" {
return true
}
return false
}