add overwrite arg
This commit is contained in:
549
main.go
549
main.go
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user