Makefile: add pickrand
[cmccabe-bin] / superrip.rb
index 3826483..e7acb5e 100755 (executable)
@@ -16,14 +16,14 @@ require 'ostruct'
 #-----------------------------------------------------------------
 # constants
 #-----------------------------------------------------------------
-$cd_dev = "/dev/cdrom"
+$children = Hash.new
 
 #-----------------------------------------------------------------
 # functions
 #-----------------------------------------------------------------
 def my_system(cmd)
   puts cmd
-  system(cmd) unless $opts.dry_run
+  system(cmd) unless $opts.dry_run == true
   ($?.exitstatus == 0) or raise "#{cmd} failed"
 end
 
@@ -34,36 +34,69 @@ end
 
 def get_number_of_tracks_on_cd
   look_for_tracks = false
-  IO.popen("cdda2wav -v summary -J dev=#{$cd_dev} 2>&1", "r") do |io|
+  lines = Array.new
+  IO.popen("cdda2wav -v summary -J dev=#{$opts.cd_dev} 2>&1", "r") do |io|
     io.readlines.each do |line|
       line.chomp!
+      lines << line
       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!"
+        line =~ /[ \t]*1-([ 1234567890][1234567890]*)[^1234567890]/ \
+          or raise "couldn't understand cdda2wav output! (line:#{line})"
         return $1.to_i
       end
     end
   end
-  raise "couldn't find what we were looking for in cdda2wav output!"
+  raise "couldn't find what we were looking for in cdda2wav output! \
+output:#{lines.join('\n')}"
 end
 
-def audiorip(track, number)
+# Process the WAV file into an MP3 and FLAC file.
+# This is done in a background process.
+def process_wav(track)
+  FileUtils.mkdir_p(track.flac_dir, verbose: true, noop: !$opts.dry_run)
+  my_system("flac -f '#{track.wav_file_name}' \
+--output-name='#{track.flac_file_name}' &>/dev/null")
+  my_system("flac --test '#{track.flac_file_name}' &>/dev/null")
+  FileUtils.mkdir_p(track.mp3_dir, verbose: true, noop: !$opts.dry_run)
+  my_system("lame -q 1 -b 192 '#{track.wav_file_name}' \
+'#{track.mp3_file_name}' &>/dev/null")
+  FileUtils.rm_f(track.wav_file_name, verbose: true, noop: !$opts.dry_run)
+end
+
+def audiorip(tnum, track)
   begin
-    my_system("nice -1 cdparanoia -w -d #{$cd_dev} #{number}")
+    my_system("nice -1 cdparanoia -w -d #{$opts.cd_dev} #{tnum}")
   rescue
-    raise "failed to rip track #{number} (#{track.name})"
+    raise "failed to rip track #{tnum} (#{track.name})"
   end
   # cdparanoia always outputs to cdda.wav
-  FileUtils.mv("cdda.wav", track.wav_file_name, $fu_args)
+  FileUtils.mv("cdda.wav", track.wav_file_name, verbose: true, noop: !$opts.dry_run)
 
-  # 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)
+  # If there are too many processes, wait for one of them to terminate
+  if ($children.keys.length > $opts.max_children) then
+    pid, status = Process.wait2(-1)
+    if (status.exitstatus != 0) then
+      raise "process #{pid} failed with exitstatus #{status.exitstatus}"
+    end
+    $children.delete(pid)
+  end
+
+  pid = Process.fork
+  if (pid == nil) then
+    retcode = 0
+    begin
+      process_wav(track)
+    rescue Exception => e
+      puts "*** FATAL ERROR: #{e}"
+      retcode = 1
+    end
+    Kernel.exit(retcode)
+  else
+    $children[pid] = 1
+  end
 end
 
 #-----------------------------------------------------------------
@@ -73,16 +106,20 @@ class MyOptions
   def self.parse(args)
     opts = OpenStruct.new
     opts.dry_run = false
-    $fu_args = { :verbose => true }
+    opts.max_children = 4
+    opts.cd_dev = "/dev/cdrom"
 
     # Fill in opts values
     parser = OptionParser.new do |myparser|
       myparser.banner = "Usage: #{ File.basename($0) } [opts]"
       myparser.separator("Specific options:")
+      myparser.on("--dev [DEV]", "-D",
+                  "choose the cdrom device file to use") do |dev|
+        opts.cd_dev = dev
+      end
       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 [FILE]", "-t",
             "Provide a list of tracks to use.") do |file|
@@ -94,6 +131,14 @@ class MyOptions
         opts.manifest_file = file
         opts.partial = true
       end
+      myparser.on("--max-children [NCHILD]", "-j",
+            "The maximum number of child processes to allow at any \
+given time") do |nchild|
+        opts.max_children = nchild.to_i
+        if (opts.max_children < 1) then
+          raise "can't set max_children to #{opts.max_children}"
+        end
+      end
     end
     parser.parse!(args)
 
@@ -103,7 +148,8 @@ class MyOptions
 end
 
 class Track
-  attr_accessor :name, :flac_dir, :flac_file_name, :mp3_dir, :mp3_file_name
+  attr_accessor :name, :flac_dir, :flac_file_name, :mp3_dir, :mp3_file_name,
+    :wav_file_name
   def initialize(name)
     if name =~ /\[LL\]/ then
       raise "you can't include [LL] in a track name" 
@@ -117,15 +163,15 @@ class Track
     (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"
+    @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
 
   def inspect
-    "#{@name}"
+    "track(\"#{@name}\")"
   end
 end
 
@@ -145,11 +191,16 @@ class Manifest
     if (@t.empty?) then
       raise "you must define some tracks"
     end
-    @t.each { |t| t.validate }
-    if (not $opts.partial) then
-      (1..num_tracks).each do |t|
-        if not @t[t].defined?
-          raise "don't know what to do with track #{t}"
+    if ($opts.partial) then
+      highest_track = @t.keys.sort[-1]
+      if (num_tracks < highest_track) then
+        raise "can't rip track #{highest_track}, because there are \
+only #{num_tracks} tracks"
+      end
+    else
+      (1..num_tracks).each do |tnum|
+        if not @t.has_key?(tnum)
+          raise "don't know what to do with track #{tnum}"
         end
       end
     end
@@ -158,9 +209,15 @@ class Manifest
   end
 
   def rip(num_tracks)
-    (1..num_tracks).each do |t|
-      next unless @t.defined?(t)
-      audiorip(t)
+    (1..num_tracks).each do |tnum|
+      next unless @t.has_key?(tnum)
+      audiorip(tnum, @t[tnum])
+    end
+    prc = Process.waitall
+    prc.each do |pair|
+      if (pair[1].exitstatus != 0) then
+        raise "process #{pair[0]} failed with exitstatus #{pair[1].exitstatus}"
+      end
     end
   end
 
@@ -192,8 +249,10 @@ die_unless_installed("cdparanoia")
 die_unless_installed("cdda2wav")
 
 manifest = Manifest.new($opts.manifest_file)
-#puts manifest.inspect
+puts manifest.inspect
 num_tracks = get_number_of_tracks_on_cd()
 puts "found #{num_tracks} tracks"
-#manifest.rip(num_tracks)
+manifest.validate(num_tracks)
+manifest.rip(num_tracks)
+puts "*** FINISHED ***"
 exit 0