From: Colin P. Mccabe Date: Wed, 19 Dec 2018 23:01:01 +0000 (-0800) Subject: Add audiobooker X-Git-Url: http://club.cc.cmu.edu/~cmccabe/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d603c8abd11dcfaa961ad0d52f11bf0ad502170a;p=cmccabe-bin Add audiobooker --- diff --git a/.gitignore b/.gitignore index 9d11a70..74827ca 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ hexconv msgpack-translate directory_merge pickrand +audiobooker # # Normal rules diff --git a/audiobooker.go b/audiobooker.go new file mode 100644 index 0000000..c330c89 --- /dev/null +++ b/audiobooker.go @@ -0,0 +1,229 @@ +package main + +import ( + "os/exec" + "bufio" + "io" + "path/filepath" + "io/ioutil" + "fmt" + "os" + "sort" + "path" + "strings" +) + +const MP3_SUFFIX = ".mp3" + +const TEMP_FILE_PREFIX = "audiobooker_" + +func checkInPath(inPath string) error { + if !strings.HasSuffix(inPath, MP3_SUFFIX) { + return fmt.Errorf("%s: not an mp3 file.\n", inPath) + } + _, err := os.Stat(inPath); if err != nil { + return fmt.Errorf("%s: %s", inPath, err) + } + return nil +} + +func checkInPaths(inPaths []string) error { + for i := range inPaths { + err := checkInPath(inPaths[i]); if err != nil { + return err + } + } + return nil +} + +func promptInPaths(inPaths []string) error { + fmt.Printf("FILES TO PROCESS:\n") + for i := range inPaths { + fmt.Printf("%s\n", inPaths[i]) + } + fmt.Printf("PROCESS FILES? [Y/N]\n") + scanner := bufio.NewScanner(os.Stdin) + if !scanner.Scan() { + return fmt.Errorf("EOF reached on stdin.") + } + if scanner.Err() != nil { + return fmt.Errorf("Error reading stdin: %s.", scanner.Err().Error()) + } + text := scanner.Text() + if text == "Y" || text == "y" { + return nil + } + if text == "N" || text == "n" { + return fmt.Errorf("User declined.") + } + return fmt.Errorf("Unknown input: %s.", text) +} + +type OutDir struct { + outPath string + nextIndex int +} + +func checkDirectoryIsEmpty(dirPath string) error { + d, err := os.Open(dirPath); if err != nil { + return fmt.Errorf("Unable to open directory %s: %s", dirPath, err.Error()) + } + defer d.Close() + names, err := d.Readdirnames(3); if err != nil && err != io.EOF { + return fmt.Errorf("Unable to readdir %s: %s", dirPath, err.Error()) + } + for i := range names { + if names[i] != "." || names[i] != ".." { + return fmt.Errorf("Directory %s is not empty: contained %s", dirPath, names[i]) + } + } + return nil +} + +func createOutDir(outPath string) (*OutDir, error) { + err := checkDirectoryIsEmpty(outPath); if err != nil { + return nil, fmt.Errorf("Invalid output directory: %s", err.Error()) + } + return &OutDir { + outPath: outPath, + nextIndex: 0, + }, nil +} + +func copyFileContents(dst, src string) error { + in, err := os.Open(src) + if err != nil { + return fmt.Errorf("Unable to open %s: %s", src, err.Error()) + } + defer in.Close() + out, err := os.Create(dst) + if err != nil { + return fmt.Errorf("Unable to create %s: %s", dst, err.Error()) + } + defer out.Close() + if _, err = io.Copy(out, in); err != nil { + return fmt.Errorf("Error copying data from %s to %s: %s", + src, dst, err.Error()) + } + err = out.Sync() + if err != nil { + return fmt.Errorf("Failed to write %s: %s", dst, err.Error()) + } + return err +} + +func (out *OutDir) CopyFile(src string) error { + dst := path.Join(out.outPath, + fmt.Sprintf("%010d%s", out.nextIndex, filepath.Ext(src))) + err := copyFileContents(dst, src) + out.nextIndex++ + return err +} + +type InContext struct { + inPath string // The original input file path. + tempDir string // The path to the temporary directory. + srcPath string // The path to the copy of the source file. +} + +func createInContext(inPath string) (*InContext, error) { + tempDir, err := ioutil.TempDir(os.TempDir(), TEMP_FILE_PREFIX); if err != nil { + return nil, fmt.Errorf("Error creating temp dir: %s", err.Error()) + } + in := &InContext { + inPath: inPath, + tempDir: tempDir, + srcPath: path.Join(tempDir, "src"), + } + err = copyFileContents(in.srcPath, inPath) + if err != nil { + in.Cleanup() + return nil, err + } + return in, nil +} + +func (in *InContext) Split() error { + command := exec.Command("mp3splt", "-t", "5.0", "-n", "-f", + "-o", TEMP_FILE_PREFIX + "@n", in.srcPath) + err := command.Run() + if err != nil { + return fmt.Errorf("mp3splt failed on %s: %s", in.srcPath, err.Error()) + } + return nil +} + +func (in *InContext) GetPaths() ([]string, error) { + d, err := os.Open(in.tempDir); if err != nil { + return nil, fmt.Errorf("Unable to open directory %s: %s", in.tempDir, err.Error()) + } + defer d.Close() + names, err := d.Readdirnames(0); if err != nil && err != io.EOF { + return nil, fmt.Errorf("Unable to readdir %s: %s", in.tempDir, err.Error()) + } + inPathBase := filepath.Base(in.srcPath) + paths := make([]string, 0, len(names)) + for i := range names { + if names[i] != "." && names[i] != ".." && names[i] != inPathBase { + paths = append(paths, path.Join(in.tempDir, names[i])) + } + } + sort.Strings(paths) + return paths, nil +} + +func (in *InContext) Cleanup() error { + return os.RemoveAll(in.tempDir) +} + +func printUsage() { + fmt.Printf("audiobooker: splits audiobook mp3 files into 5 minute segments.\n") + fmt.Printf("usage: audiobooker [mp3 paths...]\n") + fmt.Printf("\n") + fmt.Printf("Note: the split mp3s will be stored in the current working directory.\n") +} + +func main() { + if len(os.Args) < 2 { + printUsage() + os.Exit(0) + } + inPaths := os.Args[1:] + err := checkInPaths(inPaths); if err != nil { + fmt.Printf("Error: %s\n", err.Error()) + os.Exit(1) + } + err = promptInPaths(inPaths); if err != nil { + fmt.Printf("%s\n", err.Error()) + os.Exit(1) + } + outDir, err := createOutDir("."); if err != nil { + fmt.Printf("%s\n", err.Error()) + os.Exit(1) + } + var totalFiles int64 + for i := range inPaths { + in, err := createInContext(inPaths[i]); if err != nil { + fmt.Printf("Error processing %s: %s", inPaths[i], err.Error()) + os.Exit(1) + } + defer in.Cleanup() + err = in.Split(); if err != nil { + fmt.Printf("%s\n", err.Error()) + os.Exit(1) + } + paths, err := in.GetPaths(); if err != nil { + fmt.Printf("Error processing %s: %s", inPaths[i], err.Error()) + os.Exit(1) + } + for j := range paths { + err = outDir.CopyFile(paths[j]); if err != nil { + fmt.Printf("Error processing %s: %s", inPaths[i], err.Error()) + os.Exit(1) + } + } + fmt.Printf("Split %s into %d file(s)\n", inPaths[i], len(paths)) + totalFiles += int64(len(paths)) + } + fmt.Printf("Processed %d total file(s)\n", totalFiles) +}