16 func usage(retval int) {
17 fmt.Printf("superrip2: rips a music CD to directories of mp3s and flacs.\n")
22 var cdRomDevPath string
23 var titleFilePath string
24 var wavDirectory string
26 rand.Seed(time.Now().UnixNano())
27 flag.StringVar(&cdRomDevPath, "d", "", "If you want to run cdparanoia, the path to the cdrom device.")
28 flag.StringVar(&titleFilePath, "t", "", "The path to the track title files.")
29 flag.StringVar(&wavDirectory, "w", "", "If the files have already been ripped into a directory " +
30 "and you want to start from there, the wav directory to look at.")
32 fmt.Printf("superrip2: rips a music CD to directories of mp3s and flacs.\n")
34 fmt.Printf("This program relies on a track title file which contains one line per track.\n")
35 fmt.Printf("For example, the lines for a classical music CD with 4 tracks might be:\n")
37 fmt.Printf("Mozart - K412 Horn concerto in D Major/01 - Allegro\n")
38 fmt.Printf("Mozart - K412 Horn concerto in D Major/02 - Rondo (allegro)\n")
39 fmt.Printf("Mozart - K417 Horn concerto in E flat major/01 - Allegro maestoso\n")
40 fmt.Printf("Mozart - K417 Horn concerto in E flat major/02 - Andante\n")
42 fmt.Printf("The program will then create the K412 and K417 directories, as well\n")
43 fmt.Printf("as flac versions of those directories.\n")
48 if titleFilePath == "" {
49 fmt.Printf("error: you must supply a title file path with -f. Pass -h for help.\n")
52 if wavDirectory == "" && cdRomDevPath == "" {
53 fmt.Printf("error: you must supply either a wav directory path or a cdrom device path. " +
54 "Pass -h for help.\n")
57 tl, err := loadTrackList(titleFilePath)
59 fmt.Printf("error loading tracklist: %s\n", err.Error())
63 var wavDirectoryToRemove string
64 if wavDirectory != "" {
65 wd, err = loadWavDir(wavDirectory)
67 fmt.Printf("error loading wav directory: %s\n", err.Error())
71 wd, err = runCdParanoia(cdRomDevPath)
73 fmt.Printf("error running cdparanoia: %s\n", err.Error())
76 wavDirectoryToRemove = wd.basePath
80 fmt.Printf("error processing wav directory: %s\n", err.Error())
83 if wavDirectoryToRemove != "" {
84 err = os.RemoveAll(wavDirectoryToRemove)
86 fmt.Printf("failed to remove %s: %s\n", wavDirectoryToRemove, err.Error())
98 func trackFromLine(line string) (*Track, error) {
99 sep := strings.IndexRune(line, '/')
101 return nil, fmt.Errorf("Unable to find slash separator in track name %s", line)
105 title: line[sep+1:len(line)],
107 if strings.HasSuffix(t.title, ".mp3") {
108 return nil, fmt.Errorf("Track title should not end in .mp3 for %s", line)
110 if strings.IndexRune(t.title, '/') != -1 {
111 return nil, fmt.Errorf("Only album and title are allowed, not multiple directory layers, for %s", line)
116 func (t *Track) String() string {
117 return fmt.Sprintf("Track(album=%s, title=%s)", t.album, t.title)
120 type TrackList []*Track
122 func loadTrackList(p string) (TrackList, error) {
127 return tl, fmt.Errorf("Unable to open tracklist file %s: %s", p, err.Error())
130 scanner := bufio.NewScanner(f)
133 t, err := trackFromLine(scanner.Text())
135 return tl, fmt.Errorf("Error parsing line %d of %s: %s", lineNo, p, err.Error())
143 type WavDirectory struct {
148 func loadWavDir(p string) (*WavDirectory, error) {
149 infos, err := ioutil.ReadDir(p)
151 return nil, fmt.Errorf("ioutil.ReadDir failed on wave file directory %s: %s", p, err.Error())
153 fileNames := make([]string, len(infos))
154 for i := range(infos) {
155 if (infos[i].IsDir()) {
156 return nil, fmt.Errorf("wav directory %s unexpectedly contained another directory " +
157 "named %s", p, infos[i].Name())
159 fileNames[i] = infos[i].Name()
161 return &WavDirectory{
163 fileNames: fileNames,
167 func runCdParanoia(cdRomDevPath string) (*WavDirectory, error) {
168 tempDir := filepath.Join(".", fmt.Sprintf("cdparanoiaTemp%d%d", rand.Int(), rand.Int()))
169 err := os.Mkdir(tempDir, 0755)
171 return nil, fmt.Errorf("Failed to create directory %s: %s", tempDir, err.Error())
173 cmd := exec.Command("cdparanoia", "-B", "-d", cdRomDevPath)
174 cmd.Stdout = os.Stdout
175 cmd.Stderr = os.Stderr
179 return nil, fmt.Errorf("Failed to run cdparanoia: %s", err.Error())
181 return loadWavDir(tempDir)
184 func mkdirIfNeeded(p string, prevDirs map[string]bool, what string) error {
185 if prevDirs[p] == true {
189 err := os.MkdirAll(p, 0755)
191 return fmt.Errorf("Unable to mkdir %s %s: %s", what, p, err.Error())
196 func (wd *WavDirectory) Process(tl TrackList) error {
197 if len(tl) != len(wd.fileNames) {
198 return fmt.Errorf("Found %d track(s) in track list but %d in wav directory",
199 len(tl), len(wd.fileNames))
201 prevDirs := make(map[string]bool)
204 err := mkdirIfNeeded(filepath.Join(".", t.album), prevDirs, "mp3 directory")
208 err = mkdirIfNeeded(filepath.Join(".", t.album) + " [LL]", prevDirs, "flac directory")
215 p := filepath.Join(wd.basePath, wd.fileNames[i])
216 mp3Path, err := generateMp3(p)
217 newMp3Path := filepath.Join(".", t.album, t.title) + ".mp3"
219 return fmt.Errorf("Error generating mp3 file for %s: %s", p, err.Error())
221 err = os.Rename(mp3Path, newMp3Path)
223 return fmt.Errorf("Unable to rename: %s", err.Error())
225 flacPath, err := generateFlac(p)
227 return fmt.Errorf("Error generating flac file for %s: %s", p, err.Error())
229 newFlacPath := filepath.Join(".", t.album + " [LL]", t.title) + ".flac"
230 err = os.Rename(flacPath, newFlacPath)
232 return fmt.Errorf("Unable to rename: %s", err.Error())
238 func generateMp3(p string) (string, error) {
239 cmd := exec.Command("lame", "-q", "2", "-b", "256", p, p + ".mp3")
242 return "", fmt.Errorf("Failed to run lame on %s: %s", p, err.Error())
244 return p + ".mp3", nil
247 func generateFlac(p string) (string, error) {
248 cmd := exec.Command("flac", p, "-o", p + ".flac")
251 return "", fmt.Errorf("Failed to run flac on %s: %s", p, err.Error())
253 return p + ".flac", nil
256 func (wd *WavDirectory) String() string {
257 return fmt.Sprintf("WavDirectory(basePath=%s, fileNames=%s)", wd.basePath, wd.fileNames)