// Command find-and-enflac walks a directory tree, finds every // uncompressed PCM audio file, and losslessly compresses them all in // parallel. An original is deleted only after its encoded replacement // has been verified to decode back to bit-identical audio. // // Integer PCM becomes FLAC. IEEE float PCM becomes WavPack, which is // the only common lossless codec that stores float samples exactly; // FLAC has no float sample type at all. package main import ( "context" "flag" "fmt" "log/slog" "os" "os/exec" "os/signal" "runtime" "sync" "sync/atomic" "syscall" ) func main() { jobs := flag.Int("jobs", runtime.NumCPU(), "files to encode concurrently") threads := flag.Int("threads", runtime.NumCPU(), "threads per ffmpeg") dryRun := flag.Bool("n", false, "report what would be done, change nothing") flag.Usage = func() { fmt.Fprintf(os.Stderr, "usage: %s [flags] [directory]\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Losslessly compress PCM audio under directory (default \".\").\n\n") flag.PrintDefaults() } flag.Parse() if flag.NArg() > 1 { flag.Usage() os.Exit(2) } root := "." if flag.NArg() == 1 { root = flag.Arg(0) } if err := run(root, *jobs, *threads, *dryRun); err != nil { slog.Error("fatal", "err", err) os.Exit(1) } } func run(root string, jobs, threads int, dryRun bool) error { for _, tool := range []string{"ffmpeg", "ffprobe"} { if _, err := exec.LookPath(tool); err != nil { return fmt.Errorf("%s not found in PATH: %w", tool, err) } } if jobs < 1 || threads < 1 { return fmt.Errorf("jobs and threads must be at least 1") } info, err := os.Stat(root) if err != nil { return err } if !info.IsDir() { return fmt.Errorf("not a directory: %s", root) } // Ctrl-C cancels in-flight ffmpeg runs; a killed encode fails // verification, so its partial output is removed and its source // is left alone. ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() srcs, err := findPCM(root) if err != nil { return err } if len(srcs) == 0 { slog.Info("no PCM audio found", "root", root) return nil } slog.Info("found PCM audio", "files", len(srcs), "jobs", jobs, "threads", threads) enc := &encoder{threads: threads, dryRun: dryRun} var failed atomic.Int64 queue := make(chan string) var wg sync.WaitGroup for range jobs { wg.Go(func() { for src := range queue { if err := enc.encodeOne(ctx, src); err != nil { slog.Error("failed", "src", src, "err", err) failed.Add(1) } } }) } for _, src := range srcs { if ctx.Err() != nil { break } queue <- src } close(queue) wg.Wait() if n := failed.Load(); n > 0 { return fmt.Errorf("%d of %d file(s) failed", n, len(srcs)) } if ctx.Err() != nil { return ctx.Err() } return nil }