--- /dev/null
+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)
+}