11 attr_reader :id, :name
14 @name = mb.result(MusicBrainz::Query::TrackGetTrackName)
16 tmp = mb.result(MusicBrainz::Query::TrackGetTrackId)
17 @id = mb.id_from_url(tmp)
22 attr_reader :id, :name
25 tmp = mb.result(MusicBrainz::Query::AlbumGetAlbumArtistId)
26 @id = mb.id_from_url(tmp)
28 @name = mb.result(MusicBrainz::Query::AlbumGetArtistName, 1)
33 attr_reader :id, :name, :artist, :tracks
34 attr_accessor :genre, :year
40 @artist = Artist.new(mb)
42 tmp = mb.result(MusicBrainz::Query::AlbumGetAlbumId)
43 @id = mb.id_from_url(tmp)
45 @name = mb.result(MusicBrainz::Query::AlbumGetAlbumName)
47 num_tracks = mb.result(MusicBrainz::Query::AlbumGetNumTracks).to_i
48 @tracks = Array.new(num_tracks)
50 1.upto(num_tracks) do |j|
51 mb.select(MusicBrainz::Query::SelectTrack, j)
53 @tracks[j - 1] = Track.new(mb)
55 mb.select(MusicBrainz::Query::Back)
61 def initialize(config)
65 def encode(dest, metadata)
68 def apply_tags(dest, metadata)
71 def compute_replaygain
78 class VorbisEncoder < Encoder
81 oggenc -q #{@cfg.vorbis_quality} -Q -o "#{dest}" -
85 def apply_tags(dest, m)
86 Tempfile.open("rbrip.vorbiscomments") do |tf|
87 tf.puts("artist=" + m[:artist])
88 tf.puts("title=" + m[:track_name])
89 tf.puts("album=" + m[:disc])
90 tf.puts("tracknumber=" + m[:track_no])
91 tf.puts("genre=" + m[:genre])
92 tf.puts("date=" + m[:year].to_s)
93 tf.puts("musicbrainz_albumid=" + m[:disc_id])
94 tf.puts("musicbrainz_artistid=" + m[:artist_id])
95 tf.puts("musicbrainz_trackid=" + m[:track_id])
98 FileUtils.mv(dest, dest + ".untagged")
99 `vorbiscomment -w "#{dest}.untagged" "#{dest}" -c #{tf.path}`
100 FileUtils.rm(dest + ".untagged")
104 def compute_replaygain(files)
105 "vorbisgain -a " + files.map { |f| f + suffix }.join(" ")
113 class FlacEncoder < Encoder
117 --sign=signed --endian=little --channels=2 --bps=16 --sample-rate=44100
125 def apply_tags(dest, m)
126 Tempfile.open("rbrip.vorbiscomments") do |tf|
127 tf.puts("artist=" + m[:artist])
128 tf.puts("title=" + m[:track_name])
129 tf.puts("album=" + m[:disc])
130 tf.puts("tracknumber=" + m[:track_no])
131 tf.puts("genre=" + m[:genre])
132 tf.puts("date=" + m[:year].to_s)
133 tf.puts("musicbrainz_albumid=" + m[:disc_id])
134 tf.puts("musicbrainz_artistid=" + m[:artist_id])
135 tf.puts("musicbrainz_trackid=" + m[:track_id])
138 `metaflac --import-tags-from=#{tf.path} #{dest}`
142 def compute_replaygain(files)
143 "metaflac --add-replay-gain " + files.map { |f| f + suffix }.join(" ")
153 # cut leading and trailing "..."
154 if self[0..2] == "..."
160 if tmp[-3..-1] == "..."
164 tmp.gsub(/\s+/, "_").gsub(/&/, "and").delete(",'()[]/\\!?\":").
166 squeeze(" ").squeeze(".").squeeze("_")
174 n = [total - length, 0].max
179 cfg_file = ARGV.first || File.expand_path("~/.rbrip.yaml")
182 config = YAML.load(File.open(cfg_file))
184 config = OpenStruct.new(
185 :device => "/dev/hdd",
187 :dirmask => "%a/%y-%l",
188 :filemask => "%n-%a-%t",
189 :sanitize_filenames => true,
193 File.open(cfg_file, "w") { |f| YAML.dump(config, f) }
196 mb = MusicBrainz::Client.new
197 mb.device = config.device
199 unless mb.query(MusicBrainz::Query::GetCDInfo)
200 STDERR.puts "Error: " + mb.error
204 num_albums = mb.result(MusicBrainz::Query::GetNumAlbums).to_i
207 STDERR.puts "Album not found. Guess you need to submit it:"
211 STDERR.puts "Multiple albums found. What now?"
215 mb.select(MusicBrainz::Query::Rewind)
216 mb.select(MusicBrainz::Query::SelectAlbum, 1)
218 album = Album.new(mb)
221 STDERR.puts "Album not found. Guess you need to submit it:"
227 Artist: #{album.artist.name.rpad(20)} (#{album.artist.id})
228 Name: #{album.name.rpad(20)} (#{album.id})
232 album.tracks.each_with_index do |t, i|
233 #tmp = GLib.locale_from_utf8(t.name)
235 puts "%3s." % (i + 1) + " #{tmp}"
238 print "\nContinue [Y/n]? "
240 exit if STDIN.getc == ?n
242 print "\nEnter year of release: "
245 album.year = STDIN.gets.strip.to_i
247 print "Enter genre: "
250 album.genre = STDIN.gets.strip
252 # where to put the encoded files?
253 destdir = config.dirmask.dup
256 "%A" => album.artist.name.sanitize,
257 "%L" => album.name.sanitize,
258 "%Y" => album.year.to_s
261 if config.sanitize_filenames
262 dir_map.each_value { |v| v.sanitize! }
265 dir_map.each do |k, v|
266 destdir.gsub!(/#{k}/, v)
267 destdir.gsub!(/#{k.downcase}/, v.downcase)
270 destdir = File.join(config.destdir, destdir)
271 FileUtils.makedirs(destdir) unless File.directory?(destdir)
273 puts "\nRipping to #{destdir}\n\n"
276 enc = VorbisEncoder.new(config)
277 #enc = FlacEncoder.new(config)
280 :artist => album.artist.name,
281 :artist_id => album.artist.id,
283 :disc_id => album.id,
284 :genre => album.genre,
287 file_map = dir_map.dup
289 album.tracks.each_with_index do |t, i|
293 file = config.filemask.dup
295 file_map["%N"] = "%02i" % track_no
297 if config.sanitize_filenames
298 file_map["%T"] = t.name.sanitize
300 file_map["%T"] = t.name
303 metadata[:track_no] = track_no.to_s
304 metadata[:track_name] = t.name
305 metadata[:track_id] = t.id
307 file_map.each do |k, v|
308 file.gsub!(/#{k}/, v)
309 file.gsub!(/#{k.downcase}/, v.downcase)
312 if config.sanitize_filenames
318 file = File.join(destdir, file)
321 rip_cmd = "cdparanoia -q -d \"#{config.device}\" -w -- #{track_no} -"
322 encode_cmd = enc.encode(file + enc.suffix, metadata)
324 puts "Ripping: #{track_no}. #{t.name} => #{file + enc.suffix}"
325 `#{rip_cmd} | #{encode_cmd}`
327 puts "Applying tags..."
328 enc.apply_tags(file + enc.suffix, metadata)
331 rplgain_cmd = enc.compute_replaygain(files)
333 unless rplgain_cmd.nil?
334 puts "Computing replaygain..."
335 `#{rplgain_cmd} > /dev/null`