#!/usr/local/bin/ruby -w # # $Id: cdda2mp3,v 1.3 2001/07/11 22:43:56 sethk Exp $ DEF_GENRE = "Industrial" DEF_YEAR = Time::now.year class TrackList COMM = "FreeBSD is the only OS." CDA_CMD = "cda" attr_reader :artist, :album, :tracks, :ntracks private def parse(f) tn = 0 while l = f.gets l.chomp! case l when /^Disc ID: /, /^$/, /^Total Time: \d\d:\d\d$/ when /^\(unknown disc title\)$/ @artist, @album = "Unknown", "Unknown" when /^ (\d\d) \d\d:\d\d (.*)\*?$/ tn+= 1 unless tn == $1.to_i puts "Track listing out of order: #{l}" return false end if $2 == "??" @tracks << "Unknown" @ntracks << "unknown" else # XXX: $2.gsub -> $2 = nil??? t = $2.gsub(/"/, "\\\"") @tracks << t @ntracks << TrackList::norm(t) end when /^(.*) \/ (.*)$/ @artist, @album = $1, $2 else puts "Unparsed line: #{l}" return false end end (@artist and @album and !@tracks.empty?) end public def TrackList::norm(os) # This could be greatly improved and compated now that this is # no longer in AWK. s = String::new(os) s.downcase! s.gsub!(" - ", "-"); s.tr!("&", "+"); s.gsub!("[?!,]", ""); s.gsub!("'", ""); s.gsub!(/\\\"/, ""); s.gsub!("re-mix", "remix"); s.gsub!(/[]\)]$/, ""); s.gsub!(/^[[\(]/, ""); s.gsub!(/ ?[[\(]/, "-"); s.gsub!(/[]\)] ?/, "-"); s.gsub!(" / ", "-"); s.tr!("/", "-"); s.tr!(" ", "_"); s.gsub!("--", "-"); s end def initialize @tracks = [] @ntracks = [] end def read_from_dev(dev) system(CDA_CMD + " -dev #{dev} on 2>&1 2>&- > /dev/null") pr = File::popen(CDA_CMD + " -dev #{dev} toc", 'r') do |p| parse(p) end system(CDA_CMD + " -dev #{dev} off 2> /dev/null") pr end def read_from_file(fn) f = File::open(fn, 'r') pr = parse(f) f.close pr end def dump puts "#{@artist}|#{@album}" puts "Dir: #{TrackList::norm(@album)}" (0...@tracks.length).each do |tn| puts "#{tn}: #{@tracks[tn]}|#{@ntracks[tn]}" end end end class Cdda2Mp3 GENRES = %w{Blues Classic\ Rock Country Dance Disco Funk Grunge Hip-Hop Jazz Metal New\ Age Oldies Other Pop R&B Rap Reggae Rock Techno Industrial Alternative Ska Death\ Metal Pranks Soundtrack Euro-Techno Ambient Trip-Hop Vocal Jazz+Funk Fusion Trance Classical Instrumental Acid House Game Sound\ Clip Gospel Noise Alt.\ Rock Bass Soul Punk Space Meditative Instrum.\ Pop Instrum.\ Rock Ethnic Gothic Darkwave Techno-Indust. Electronic Pop-Folk Eurodance Dream Southern Rock Comedy Cult Gangsta Top\ 40 Christian Rap Pop/Funk Jungle Native\ American Cabaret New\ Wave Psychadelic Rave Showtunes Trailer Lo-Fi Tribal Acid Punk Acid\ Jazz Polka Retro Musical Rock\ &\ Roll Hard\ Rock Folk Folk/Rock National Folk Swing Fusion Bebob Latin Revival Celtic Bluegrass Avantgarde Gothic\ Rock Progress.\ Rock Psychadel.\ Rock Symphonic\ Rock Slow\ Rock Big\ Band Chorus Easy\ Listening Acoustic Humour Speech Chanson Opera Chamber\ Music Sonata Symphony Booty\ Bass Primus Porn\ Groove Satire} COMMENT = "FreeBSD is the only OS." MANAGE, READ, ENCODE, TAG = 0, 1, 2, 3 protected def fnbase(n) sprintf("%s/%02d.%s", @dir, n + 1, @tl.ntracks[n]) end def status(a, n) @mode = a td = Time::now - @st $defout.write(sprintf("%02d:%02d ", td / 60, td % 60)) as = case a when READ "Read " when ENCODE "Encode" when TAG "Tag " end as+= " [" as+= " " * n as+= case a when READ "R " when ENCODE " E " when TAG " T" end as+= " " * (@tl.ntracks.length - n - 1) as+= "]" puts as end def header print "00:00 Tracks [" (1..@tl.ntracks.length).each do |n| printf "%-3d", n end puts "]" end private def sysexec(cmd) puts "Running \"#{cmd}\"" if $-d system(cmd) end def bcast(s) if @mode == MANAGE @pa.each do |c| Process::kill(c, s) end end exit 1 end def read(n) status(READ, n) puts "Reading #{fnbase(n)}.wav" if $-d sysexec("dagrab -d #{@dev} -f #{fnbase(n)}.wav #{n + 1} " + "> /dev/null") end def reader(rp) puts "Reader started" if $-d pid = fork return pid if pid (0...@tl.tracks.length).each do |tn| read(tn) rp.syswrite(tn.chr) end rp.syswrite(255.chr) rp.close exit end def enc(n) status(ENCODE, n) puts "Encoding #{fnbase(n)}.wav" if $-d #sysexec("bladeenc -br 192 -quiet #{fnbase(n)}.wav > /dev/null") sysexec("lame -S -b 192 -quiet #{fnbase(n)}.wav " + "#{fnbase(n)}.mp3 2>&1 2>&- > /dev/null") File::unlink(fnbase(n) + ".wav") end def encoder(rp, ep) puts "Encoder started" if $-d pid = fork return pid if pid until (tc = rp.getc) == 255 enc(tc) ep.putc(tc) end ep.putc(255) rp.close ep.close exit end def tag(n) status(TAG, n) puts "Tagging #{fnbase(n)}.mp3" if $-d sysexec("id3ren -quiet -tagonly -song \"#{@tl.tracks[n]}\" " + "-artist \"#{@tl.artist}\" " + "-album \"#{@tl.album}\" " + "-year \"#{@year}\" " + "-genre \"#{@genre}\" " + "-comment \"#{COMMENT}\" " + "#{fnbase(n)}.mp3 > /dev/null") end def tagger(ep) puts "Tagger started" if $-d pid = fork return pid if pid until (ec = ep.getc) == 255 tag(ec) end ep.close exit end public def Cdda2Mp3::is_genre?(g) GENRES.include?(g) end def Cdda2Mp3::list_genres puts "Possible Genres:" puts GENRES.join(", ") end def initialize(dev, year, genre, tl) throw :genre unless Cdda2Mp3::is_genre?(genre) @dev, @year, @genre, @tl = dev, year, genre, tl @dir = TrackList::norm(tl.album) end def run header @st = Time::now begin Dir::mkdir(@dir) rescue Errno::EEXIST puts "Warning: #{@dir}/ already exists" end rp = IO::pipe ep = IO::pipe @pa = [] @pa << reader(rp[1]) @pa << encoder(rp[0], ep[1]) @pa << tagger(ep[0]) @mode = MANAGE ["HUP", "INT", "TERM"].each do |s| trap s, "bcase(s)" end @pa.each do |pid| begin Process::waitpid(pid) rescue Errno::ECHILD next end end end end unless (dfn = ENV["CDROM"]) puts "Please setenv CDROM to the pathname of your CD-ROM device" exit 1 end tl = TrackList::new unless tl.read_from_dev(dfn) puts "Error reading track list" exit 1 end puts "Got track list for: #{tl.artist}: #{tl.album}" begin print "Year [#{DEF_YEAR}]: " l = gets.chomp year = if l == "" DEF_YEAR else l.to_i end end until year <= DEF_YEAR and year > 0 begin print "Genre [#{DEF_GENRE}] (\"?\" for list): " genre = gets.chomp Cdda2Mp3::list_genres if genre == "?" genre = DEF_GENRE if genre == "" end until Cdda2Mp3::is_genre?(genre) Cdda2Mp3::new(dfn, year, genre, tl).run