18 const MP3_SUFFIX = ".mp3"
20 const TEMP_FILE_PREFIX = "audiobooker_"
22 func checkInPath(inPath string) error {
23 if !strings.HasSuffix(inPath, MP3_SUFFIX) {
24 return fmt.Errorf("%s: not an mp3 file.\n", inPath)
26 _, err := os.Stat(inPath); if err != nil {
27 return fmt.Errorf("%s: %s", inPath, err)
32 func checkInPaths(inPaths []string) error {
33 for i := range inPaths {
34 err := checkInPath(inPaths[i]); if err != nil {
41 func promptInPaths(inPaths []string) error {
42 fmt.Printf("FILES TO PROCESS:\n")
43 for i := range inPaths {
44 fmt.Printf("%s\n", inPaths[i])
46 fmt.Printf("PROCESS FILES? [Y/N]\n")
47 scanner := bufio.NewScanner(os.Stdin)
49 return fmt.Errorf("EOF reached on stdin.")
51 if scanner.Err() != nil {
52 return fmt.Errorf("Error reading stdin: %s.", scanner.Err().Error())
54 text := scanner.Text()
55 if text == "Y" || text == "y" {
58 if text == "N" || text == "n" {
59 return fmt.Errorf("User declined.")
61 return fmt.Errorf("Unknown input: %s.", text)
69 func checkDirectoryIsEmpty(dirPath string) error {
70 d, err := os.Open(dirPath); if err != nil {
71 return fmt.Errorf("Unable to open directory %s: %s", dirPath, err.Error())
74 names, err := d.Readdirnames(3); if err != nil && err != io.EOF {
75 return fmt.Errorf("Unable to readdir %s: %s", dirPath, err.Error())
77 for i := range names {
78 if names[i] != "." || names[i] != ".." {
79 return fmt.Errorf("Directory %s is not empty: contained %s", dirPath, names[i])
85 func createOutDir(outPath string) (*OutDir, error) {
86 err := checkDirectoryIsEmpty(outPath); if err != nil {
87 return nil, fmt.Errorf("Invalid output directory: %s", err.Error())
95 func copyFileContents(dst, src string) error {
96 in, err := os.Open(src)
98 return fmt.Errorf("Unable to open %s: %s", src, err.Error())
101 out, err := os.Create(dst)
103 return fmt.Errorf("Unable to create %s: %s", dst, err.Error())
106 if _, err = io.Copy(out, in); err != nil {
107 return fmt.Errorf("Error copying data from %s to %s: %s",
108 src, dst, err.Error())
112 return fmt.Errorf("Failed to write %s: %s", dst, err.Error())
117 func (out *OutDir) CopyFile(src string) error {
118 dst := path.Join(out.outPath,
119 fmt.Sprintf("%010d%s", out.nextIndex, filepath.Ext(src)))
120 err := copyFileContents(dst, src)
125 func (out *OutDir) GetNumDigitsNeeded() int {
126 i := math.Log10(float64(out.nextIndex))
135 func (out *OutDir) RenameOutputFiles() error {
136 numDigitsNeeded := out.GetNumDigitsNeeded()
137 fs := "%0" + strconv.Itoa(numDigitsNeeded) + "d" + MP3_SUFFIX
138 d, err := os.Open(out.outPath); if err != nil {
139 return fmt.Errorf("Unable to open directory %s: %s", out.outPath, err.Error())
142 names, err := d.Readdirnames(0); if err != nil && err != io.EOF {
143 return fmt.Errorf("Unable to readdir %s: %s", out.outPath, err.Error())
145 for i := range names {
147 _, err = fmt.Sscanf(names[i], "%d", &index); if err != nil {
148 return fmt.Errorf("Unable to parse file name %s", names[i])
150 newName := fmt.Sprintf(fs, index)
151 src := path.Join(out.outPath, names[i])
152 dst := path.Join(out.outPath, newName)
153 err = os.Rename(src, dst)
155 return fmt.Errorf("Failed to rename %s to %s: %s", src, dst, err.Error())
161 type InContext struct {
162 inPath string // The original input file path.
163 tempDir string // The path to the temporary directory.
164 srcPath string // The path to the copy of the source file.
167 func createInContext(inPath string) (*InContext, error) {
168 tempDir, err := ioutil.TempDir(os.TempDir(), TEMP_FILE_PREFIX); if err != nil {
169 return nil, fmt.Errorf("Error creating temp dir: %s", err.Error())
174 srcPath: path.Join(tempDir, "src"),
176 err = copyFileContents(in.srcPath, inPath)
184 func (in *InContext) Split() error {
185 command := exec.Command("mp3splt", "-t", "5.0", "-n", "-f",
186 "-o", TEMP_FILE_PREFIX + "@n", in.srcPath)
189 return fmt.Errorf("mp3splt failed on %s: %s", in.srcPath, err.Error())
194 func (in *InContext) GetPaths() ([]string, error) {
195 d, err := os.Open(in.tempDir); if err != nil {
196 return nil, fmt.Errorf("Unable to open directory %s: %s", in.tempDir, err.Error())
199 names, err := d.Readdirnames(0); if err != nil && err != io.EOF {
200 return nil, fmt.Errorf("Unable to readdir %s: %s", in.tempDir, err.Error())
202 inPathBase := filepath.Base(in.srcPath)
203 paths := make([]string, 0, len(names))
204 for i := range names {
205 if names[i] != "." && names[i] != ".." && names[i] != inPathBase {
206 paths = append(paths, path.Join(in.tempDir, names[i]))
213 func (in *InContext) Cleanup() error {
214 return os.RemoveAll(in.tempDir)
218 fmt.Printf("audiobooker: splits audiobook mp3 files into 5 minute segments.\n")
219 fmt.Printf("usage: audiobooker [mp3 paths...]\n")
221 fmt.Printf("Note: the split mp3s will be stored in the current working directory.\n")
225 if len(os.Args) < 2 {
229 inPaths := os.Args[1:]
230 err := checkInPaths(inPaths); if err != nil {
231 fmt.Printf("Error: %s\n", err.Error())
234 err = promptInPaths(inPaths); if err != nil {
235 fmt.Printf("%s\n", err.Error())
238 outDir, err := createOutDir("."); if err != nil {
239 fmt.Printf("%s\n", err.Error())
243 for i := range inPaths {
244 in, err := createInContext(inPaths[i]); if err != nil {
245 fmt.Printf("Error processing %s: %s", inPaths[i], err.Error())
249 err = in.Split(); if err != nil {
250 fmt.Printf("%s\n", err.Error())
253 paths, err := in.GetPaths(); if err != nil {
254 fmt.Printf("Error processing %s: %s", inPaths[i], err.Error())
257 for j := range paths {
258 err = outDir.CopyFile(paths[j]); if err != nil {
259 fmt.Printf("Error processing %s: %s", inPaths[i], err.Error())
263 fmt.Printf("Split %s into %d file(s)\n", inPaths[i], len(paths))
264 totalFiles += int64(len(paths))
266 err = outDir.RenameOutputFiles()
268 fmt.Printf("Error renaming output files in %s: %s", outDir.outPath, err.Error())
271 fmt.Printf("Processed %d total file(s)\n", totalFiles)