--- /dev/null
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "math/rand"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+func usage(retval int) {
+ fmt.Printf("superrip2: rips a music CD to directories of mp3s and flacs.\n")
+ os.Exit(retval)
+}
+
+func main() {
+ var cdRomDevPath string
+ var titleFilePath string
+ var wavDirectory string
+
+ rand.Seed(time.Now().UnixNano())
+ flag.StringVar(&cdRomDevPath, "d", "", "If you want to run cdparanoia, the path to the cdrom device.")
+ flag.StringVar(&titleFilePath, "t", "", "The path to the track title files.")
+ flag.StringVar(&wavDirectory, "w", "", "If the files have already been ripped into a directory " +
+ "and you want to start from there, the wav directory to look at.")
+ flag.Usage = func() {
+ fmt.Printf("superrip2: rips a music CD to directories of mp3s and flacs.\n")
+ fmt.Printf("\n")
+ fmt.Printf("This program relies on a track title file which contains one line per track.\n")
+ fmt.Printf("For example, the lines for a classical music CD with 4 tracks might be:\n")
+ fmt.Printf("\n")
+ fmt.Printf("Mozart - K412 Horn concerto in D Major/01 - Allegro\n")
+ fmt.Printf("Mozart - K412 Horn concerto in D Major/02 - Rondo (allegro)\n")
+ fmt.Printf("Mozart - K417 Horn concerto in E flat major/01 - Allegro maestoso\n")
+ fmt.Printf("Mozart - K417 Horn concerto in E flat major/02 - Andante\n")
+ fmt.Printf("\n")
+ fmt.Printf("The program will then create the K412 and K417 directories, as well\n")
+ fmt.Printf("as flac versions of those directories.\n")
+ fmt.Printf("\n")
+ flag.PrintDefaults()
+ }
+ flag.Parse()
+ if titleFilePath == "" {
+ fmt.Printf("error: you must supply a title file path with -f. Pass -h for help.\n")
+ os.Exit(1)
+ }
+ if wavDirectory == "" && cdRomDevPath == "" {
+ fmt.Printf("error: you must supply either a wav directory path or a cdrom device path. " +
+ "Pass -h for help.\n")
+ os.Exit(1)
+ }
+ tl, err := loadTrackList(titleFilePath)
+ if err != nil {
+ fmt.Printf("error loading tracklist: %s\n", err.Error())
+ os.Exit(1)
+ }
+ var wd *WavDirectory
+ var wavDirectoryToRemove string
+ if wavDirectory != "" {
+ wd, err = loadWavDir(wavDirectory)
+ if err != nil {
+ fmt.Printf("error loading wav directory: %s\n", err.Error())
+ os.Exit(1)
+ }
+ } else {
+ wd, err = runCdParanoia(cdRomDevPath)
+ if err != nil {
+ fmt.Printf("error running cdparanoia: %s\n", err.Error())
+ os.Exit(1)
+ }
+ wavDirectoryToRemove = wd.basePath
+ }
+ err = wd.Process(tl)
+ if err != nil {
+ fmt.Printf("error processing wav directory: %s\n", err.Error())
+ os.Exit(1)
+ }
+ if wavDirectoryToRemove != "" {
+ err = os.RemoveAll(wavDirectoryToRemove)
+ if err != nil {
+ fmt.Printf("failed to remove %s: %s\n", wavDirectoryToRemove, err.Error())
+ os.Exit(1)
+ }
+ }
+ os.Exit(0)
+}
+
+type Track struct {
+ album string
+ title string
+}
+
+func trackFromLine(line string) (*Track, error) {
+ sep := strings.IndexRune(line, '/')
+ if sep < 0 {
+ return nil, fmt.Errorf("Unable to find slash separator in track name %s", line)
+ }
+ t := &Track{
+ album: line[0:sep],
+ title: line[sep+1:len(line)],
+ }
+ if strings.HasSuffix(t.title, ".mp3") {
+ return nil, fmt.Errorf("Track title should not end in .mp3 for %s", line)
+ }
+ if strings.IndexRune(t.title, '/') != -1 {
+ return nil, fmt.Errorf("Only album and title are allowed, not multiple directory layers, for %s", line)
+ }
+ return t, nil
+}
+
+func (t *Track) String() string {
+ return fmt.Sprintf("Track(album=%s, title=%s)", t.album, t.title)
+}
+
+type TrackList []*Track
+
+func loadTrackList(p string) (TrackList, error) {
+ var tl []*Track
+
+ f, err := os.Open(p)
+ if err != nil {
+ return tl, fmt.Errorf("Unable to open tracklist file %s: %s", p, err.Error())
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ lineNo := 1
+ for scanner.Scan() {
+ t, err := trackFromLine(scanner.Text())
+ if err != nil {
+ return tl, fmt.Errorf("Error parsing line %d of %s: %s", lineNo, p, err.Error())
+ }
+ tl = append(tl, t)
+ lineNo = lineNo + 1
+ }
+ return tl, nil
+}
+
+type WavDirectory struct {
+ basePath string
+ fileNames []string
+}
+
+func loadWavDir(p string) (*WavDirectory, error) {
+ infos, err := ioutil.ReadDir(p)
+ if err != nil {
+ return nil, fmt.Errorf("ioutil.ReadDir failed on wave file directory %s: %s", p, err.Error())
+ }
+ fileNames := make([]string, len(infos))
+ for i := range(infos) {
+ if (infos[i].IsDir()) {
+ return nil, fmt.Errorf("wav directory %s unexpectedly contained another directory " +
+ "named %s", p, infos[i].Name())
+ }
+ fileNames[i] = infos[i].Name()
+ }
+ return &WavDirectory{
+ basePath: p,
+ fileNames: fileNames,
+ }, nil
+}
+
+func runCdParanoia(cdRomDevPath string) (*WavDirectory, error) {
+ tempDir := filepath.Join(".", fmt.Sprintf("cdparanoiaTemp%d%d", rand.Int(), rand.Int()))
+ err := os.Mkdir(tempDir, 0755)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to create directory %s: %s", tempDir, err.Error())
+ }
+ cmd := exec.Command("cdparanoia", "-B", "-d", cdRomDevPath)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Dir = tempDir
+ err = cmd.Run()
+ if err != nil {
+ return nil, fmt.Errorf("Failed to run cdparanoia: %s", err.Error())
+ }
+ return loadWavDir(tempDir)
+}
+
+func mkdirIfNeeded(p string, prevDirs map[string]bool, what string) error {
+ if prevDirs[p] == true {
+ return nil
+ }
+ prevDirs[p] = true
+ err := os.MkdirAll(p, 0755)
+ if err != nil {
+ return fmt.Errorf("Unable to mkdir %s %s: %s", what, p, err.Error())
+ }
+ return nil
+}
+
+func (wd *WavDirectory) Process(tl TrackList) error {
+ if len(tl) != len(wd.fileNames) {
+ return fmt.Errorf("Found %d track(s) in track list but %d in wav directory",
+ len(tl), len(wd.fileNames))
+ }
+ prevDirs := make(map[string]bool)
+ for i := range(tl) {
+ t := tl[i]
+ err := mkdirIfNeeded(filepath.Join(".", t.album), prevDirs, "mp3 directory")
+ if err != nil {
+ return err
+ }
+ err = mkdirIfNeeded(filepath.Join(".", t.album) + " [LL]", prevDirs, "flac directory")
+ if err != nil {
+ return err
+ }
+ }
+ for i := range(tl) {
+ t := tl[i]
+ p := filepath.Join(wd.basePath, wd.fileNames[i])
+ mp3Path, err := generateMp3(p)
+ newMp3Path := filepath.Join(".", t.album, t.title) + ".mp3"
+ if err != nil {
+ return fmt.Errorf("Error generating mp3 file for %s: %s", p, err.Error())
+ }
+ err = os.Rename(mp3Path, newMp3Path)
+ if err != nil {
+ return fmt.Errorf("Unable to rename: %s", err.Error())
+ }
+ flacPath, err := generateFlac(p)
+ if err != nil {
+ return fmt.Errorf("Error generating flac file for %s: %s", p, err.Error())
+ }
+ newFlacPath := filepath.Join(".", t.album + " [LL]", t.title) + ".flac"
+ err = os.Rename(flacPath, newFlacPath)
+ if err != nil {
+ return fmt.Errorf("Unable to rename: %s", err.Error())
+ }
+ }
+ return nil
+}
+
+func generateMp3(p string) (string, error) {
+ cmd := exec.Command("lame", "-q", "2", "-b", "256", p, p + ".mp3")
+ err := cmd.Run()
+ if err != nil {
+ return "", fmt.Errorf("Failed to run lame on %s: %s", p, err.Error())
+ }
+ return p + ".mp3", nil
+}
+
+func generateFlac(p string) (string, error) {
+ cmd := exec.Command("flac", p, "-o", p + ".flac")
+ err := cmd.Run()
+ if err != nil {
+ return "", fmt.Errorf("Failed to run flac on %s: %s", p, err.Error())
+ }
+ return p + ".flac", nil
+}
+
+func (wd *WavDirectory) String() string {
+ return fmt.Sprintf("WavDirectory(basePath=%s, fileNames=%s)", wd.basePath, wd.fileNames)
+}
+