Initial rev of superrip
authorColin Patrick McCabe <cmccabe@alumni.cmu.edu>
Sat, 3 Apr 2010 07:47:51 +0000 (00:47 -0700)
committerColin Patrick McCabe <cmccabe@alumni.cmu.edu>
Sat, 3 Apr 2010 07:47:51 +0000 (00:47 -0700)
superrip.rb [new file with mode: 0755]

diff --git a/superrip.rb b/superrip.rb
new file mode 100755 (executable)
index 0000000..0b1ef6f
--- /dev/null
@@ -0,0 +1,179 @@
+#!/usr/bin/ruby -w
+
+#
+# superrip.rb
+#
+# Advanced CD-ROM ripping program
+# 
+# Copyright 2010, Colin McCabe
+#
+
+require 'fileutils'
+require 'optparse'
+require 'optparse/time'
+require 'ostruct'
+
+#-----------------------------------------------------------------
+# constants
+#-----------------------------------------------------------------
+$cd_dev = "/dev/cdrom"
+
+#-----------------------------------------------------------------
+# functions
+#-----------------------------------------------------------------
+def my_system(cmd)
+  puts cmd
+  system(cmd) unless $opts.dry_run
+  ($?.exitstatus == 0) or raise "#{cmd} failed"
+end
+
+def die_unless_installed(cmd)
+  system("which #{cmd} > /dev/null")
+  ($?.exitstatus == 0) or raise "you need to install the #{cmd} program"
+end
+
+def get_number_of_tracks_on_cd
+  look_for_tracks = false
+  IO.popen("cdda2wav -v summary -J dev=#{$cd_dev}", "r") do |io|
+    line = io.read.chomp
+    if (line =~ /^AUDIOtrack/) then
+      look_for_tracks = true
+    elsif (look_for_tracks == true) then
+      look_for_tracks = false
+      line =~ /[ \t]*1-([1234567890][1234567890]*)[^1234567890]/ \
+        or raise "couldn't understand cdda2wav output!"
+      return $1.to_i
+    end
+  end
+  raise "couldn't find what we were looking for in cdda2wav output!"
+end
+
+def audiorip(track, number)
+  begin
+    my_system("nice -1 cdparanoia -w -d #{$cd_dev} #{number}")
+  rescue
+    raise "failed to rip track #{number} (#{track.name})"
+  end
+  # cdparanoia always outputs to cdda.wav
+  FileUtils.mv("cdda.wav", track.wav_file_name, $fu_args)
+
+  # TODO: spawn a thread to do this stuff in the background
+  FileUtils.mkdir_p(track.flac_dir, $fu_args)
+  my_system("flac -c #{track.wav_file_name} > #{track.flac_file_name}")
+  my_system("lame -q 1 -b 192 #{track.wav_file_name} > #{track.mp3_file_name}")
+  FileUtils.rm_f(track.wav_file, $fu_args)
+end
+
+#-----------------------------------------------------------------
+# classes
+#-----------------------------------------------------------------
+class MyOptions
+  def self.parse(args)
+    opts = OpenStruct.new
+    opts.dry_run = false
+    $fu_args = { :verbose => true }
+
+    # Fill in opts values
+    parser = OptionParser.new do |myparser|
+      myparser.banner = "Usage: #{ File.basename($0) } [opts]"
+      myparser.separator("Specific options:")
+      myparser.on("--dry-run", "-d",
+            "Show what would be done, without doing it.") do |a|
+        opts.dry_run = true
+        $fu_args = { :verbose => true, :noop => true }
+      end
+      myparser.on("--tracklist", "-t",
+            "Provide a list of tracks to use.") do |file|
+        opts.manifest_file = file
+        opts.partial = false
+      end
+      myparser.on("--partial-tracklist", "-T",
+            "Provide a partial list of tracks to use.") do |file|
+        opts.manifest_file = file
+        opts.partial = true
+      end
+    end
+    parser.parse!(args)
+
+    raise "you must provide a tracklist" unless opts.manifest_file != nil
+    return opts
+  end
+end
+
+class Track
+  attr_accessor :name, :flac_dir, :flac_file_name, :mp3_dir, :mp3_file_name
+  def initialize(name)
+    if name =~ /\[LL\]/ then
+      raise "you can't include [LL] in a track name" 
+    end
+    if name =~ /\.mp3/ then
+      raise "don't include .mp3 in the track name; that will be added"
+    end
+    if name =~ /\.flac/ then
+      raise "don't include .flac in the track name; that will be added"
+    end
+    (name =~ /([^\/][^\/]*)\/([^\/]*[^\/])/) or \
+      raise "track name must be of the form 'foo/bar'"
+    @name = name
+    @flac_dir = "#{1} [LL]"
+    @flac_file_name = "#{@flac_dir}/#{2}.flac"
+    @mp3_dir = "#{1}"
+    @mp3_file_name = "#{@mp3_dir}/#{2}.mp3"
+    @wav_file_name = "#{1}__#{2}.wav"
+  end
+end
+
+class Manifest
+  def initialize(filename)
+    @tracks = Hash.new
+    eval(filename)
+    @tracks.each do |key, val|
+      @tracks[key] = Track.new(val)
+    end
+  end
+
+  def validate(num_tracks)
+    if (@tracks.empty?) then
+      raise "you must define some tracks"
+    end
+    @tracks.each { |t| t.validate }
+    if (not $opts.partial) then
+      (1..num_tracks).each do |t|
+        if not @tracks[t].defined?
+          raise "don't know what to do with track #{t}"
+        end
+      end
+    end
+  end
+
+  def rip(num_tracks)
+    (1..num_tracks).each do |t|
+      next unless @tracks.defined?(t)
+      audiorip(t)
+    end
+  end
+end
+
+#-----------------------------------------------------------------
+# main
+#-----------------------------------------------------------------
+# Parse options.
+begin
+  begin
+    $opts = MyOptions.parse(ARGV)
+  rescue ArgumentError => msg
+  $stderr.puts("#{msg} Type --help to see usage information.\n")
+  exit 1
+  end
+end
+
+die_unless_installed("lame")
+die_unless_installed("flac")
+die_unless_installed("cdparanoia")
+die_unless_installed("cdda2wav")
+
+manifest = Manifest.new($opts.manifest_file)
+num_tracks = get_number_of_tracks_on_cd()
+puts "num_tracks = #{num_tracks}"
+#manifest.rip(num_tracks)
+exit 0