16 const MP3_SUFFIX = ".mp3"
18 const TEMP_FILE_PREFIX = "audiobooker_"
20 func checkInPath(inPath string) error {
21 if !strings.HasSuffix(inPath, MP3_SUFFIX) {
22 return fmt.Errorf("%s: not an mp3 file.\n", inPath)
24 _, err := os.Stat(inPath); if err != nil {
25 return fmt.Errorf("%s: %s", inPath, err)
30 func checkInPaths(inPaths []string) error {
31 for i := range inPaths {
32 err := checkInPath(inPaths[i]); if err != nil {
39 func promptInPaths(inPaths []string) error {
40 fmt.Printf("FILES TO PROCESS:\n")
41 for i := range inPaths {
42 fmt.Printf("%s\n", inPaths[i])
44 fmt.Printf("PROCESS FILES? [Y/N]\n")
45 scanner := bufio.NewScanner(os.Stdin)
47 return fmt.Errorf("EOF reached on stdin.")
49 if scanner.Err() != nil {
50 return fmt.Errorf("Error reading stdin: %s.", scanner.Err().Error())
52 text := scanner.Text()
53 if text == "Y" || text == "y" {
56 if text == "N" || text == "n" {
57 return fmt.Errorf("User declined.")
59 return fmt.Errorf("Unknown input: %s.", text)
67 func checkDirectoryIsEmpty(dirPath string) error {
68 d, err := os.Open(dirPath); if err != nil {
69 return fmt.Errorf("Unable to open directory %s: %s", dirPath, err.Error())
72 names, err := d.Readdirnames(3); if err != nil && err != io.EOF {
73 return fmt.Errorf("Unable to readdir %s: %s", dirPath, err.Error())
75 for i := range names {
76 if names[i] != "." || names[i] != ".." {
77 return fmt.Errorf("Directory %s is not empty: contained %s", dirPath, names[i])
83 func createOutDir(outPath string) (*OutDir, error) {
84 err := checkDirectoryIsEmpty(outPath); if err != nil {
85 return nil, fmt.Errorf("Invalid output directory: %s", err.Error())
93 func copyFileContents(dst, src string) error {
94 in, err := os.Open(src)
96 return fmt.Errorf("Unable to open %s: %s", src, err.Error())
99 out, err := os.Create(dst)
101 return fmt.Errorf("Unable to create %s: %s", dst, err.Error())
104 if _, err = io.Copy(out, in); err != nil {
105 return fmt.Errorf("Error copying data from %s to %s: %s",
106 src, dst, err.Error())
110 return fmt.Errorf("Failed to write %s: %s", dst, err.Error())
115 func (out *OutDir) CopyFile(src string) error {
116 dst := path.Join(out.outPath,
117 fmt.Sprintf("%010d%s", out.nextIndex, filepath.Ext(src)))
118 err := copyFileContents(dst, src)
123 type InContext struct {
124 inPath string // The original input file path.
125 tempDir string // The path to the temporary directory.
126 srcPath string // The path to the copy of the source file.
129 func createInContext(inPath string) (*InContext, error) {
130 tempDir, err := ioutil.TempDir(os.TempDir(), TEMP_FILE_PREFIX); if err != nil {
131 return nil, fmt.Errorf("Error creating temp dir: %s", err.Error())
136 srcPath: path.Join(tempDir, "src"),
138 err = copyFileContents(in.srcPath, inPath)
146 func (in *InContext) Split() error {
147 command := exec.Command("mp3splt", "-t", "5.0", "-n", "-f",
148 "-o", TEMP_FILE_PREFIX + "@n", in.srcPath)
151 return fmt.Errorf("mp3splt failed on %s: %s", in.srcPath, err.Error())
156 func (in *InContext) GetPaths() ([]string, error) {
157 d, err := os.Open(in.tempDir); if err != nil {
158 return nil, fmt.Errorf("Unable to open directory %s: %s", in.tempDir, err.Error())
161 names, err := d.Readdirnames(0); if err != nil && err != io.EOF {
162 return nil, fmt.Errorf("Unable to readdir %s: %s", in.tempDir, err.Error())
164 inPathBase := filepath.Base(in.srcPath)
165 paths := make([]string, 0, len(names))
166 for i := range names {
167 if names[i] != "." && names[i] != ".." && names[i] != inPathBase {
168 paths = append(paths, path.Join(in.tempDir, names[i]))
175 func (in *InContext) Cleanup() error {
176 return os.RemoveAll(in.tempDir)
180 fmt.Printf("audiobooker: splits audiobook mp3 files into 5 minute segments.\n")
181 fmt.Printf("usage: audiobooker [mp3 paths...]\n")
183 fmt.Printf("Note: the split mp3s will be stored in the current working directory.\n")
187 if len(os.Args) < 2 {
191 inPaths := os.Args[1:]
192 err := checkInPaths(inPaths); if err != nil {
193 fmt.Printf("Error: %s\n", err.Error())
196 err = promptInPaths(inPaths); if err != nil {
197 fmt.Printf("%s\n", err.Error())
200 outDir, err := createOutDir("."); if err != nil {
201 fmt.Printf("%s\n", err.Error())
205 for i := range inPaths {
206 in, err := createInContext(inPaths[i]); if err != nil {
207 fmt.Printf("Error processing %s: %s", inPaths[i], err.Error())
211 err = in.Split(); if err != nil {
212 fmt.Printf("%s\n", err.Error())
215 paths, err := in.GetPaths(); if err != nil {
216 fmt.Printf("Error processing %s: %s", inPaths[i], err.Error())
219 for j := range paths {
220 err = outDir.CopyFile(paths[j]); if err != nil {
221 fmt.Printf("Error processing %s: %s", inPaths[i], err.Error())
225 fmt.Printf("Split %s into %d file(s)\n", inPaths[i], len(paths))
226 totalFiles += int64(len(paths))
228 fmt.Printf("Processed %d total file(s)\n", totalFiles)