#!/usr/bin/ruby require 'getoptlong' require 'id3lib' require 'flacinfo' require 'tempfile' def create_mp3(sourcefile, destfile) fork do rd, wr = IO.pipe if fork then STDOUT.reopen(wr) rd.close wr.close exec('sox', '--replay-gain=track', sourcefile, '-t', 'wav', '-') else STDIN.reopen(rd) STDOUT.reopen('/dev/null', 'w') rd.close wr.close exec('lame', '--r3mix', '-p', '-B', '192', '-V', '8', '-', destfile) end end Process.waitall puts 'Encoding finished.' end def get_flac_metadata(flacfile) return FlacInfo.new(flacfile).tags end def flac_key_to_id3v2(key) return case key # Standard fields. when "TITLE" then :TIT2 when "VERSION" then :TIT3 when "ALBUM" then :TALB when "TRACKNUMBER" then :TRCK when "ARTIST" then :TPE2 when "PERFORMER" then :TPE1 when "COPYRIGHT" then :TCOP when "LICENSE" then :TOWN when "ORGANIZATION" then :TPUB # DESCRIPTION when "GENRE" then :TCON #when "DATE" then :TDRC # LOCATION # CONTACT when "ISRC" then :TSRC # My additions when "SUBTITLE" then :TIT3 when "ORIGINAL_ALBUM" then :TOAL when "ORIGINAL_ARTIST" then :TOPE when "COMPOSER" then :TCOM when "CONDUCTOR" then :TPE3 when /^DIS[CK]NUMBER$/ then :TPOS when "YEAR" then :TYER when "URL" then :WXXX # Seen elsewhere when "BPM" then :TBPM when "ENCODER" then :TSSE end end def get_track_line(trackno, metadata) if metadata["TOTALTRACKS"] then return trackno + "/" + metadata["TOTALTRACKS"] elsif metadata["TRACKTOTAL"] then return trackno + "/" + metadata["TRACKTOTAL"] else return trackno end end def set_mp3_metadata(mp3file, metadata) tag = ID3Lib::Tag.new(mp3file, ID3Lib::V_BOTH) metadata.each { |key, value| id3v2_key = flac_key_to_id3v2(key) if id3v2_key == :TRCK then tag.set_frame_text(id3v2_key, get_track_line(value, metadata)) elsif id3v2_key then tag.set_frame_text(id3v2_key, value) end } tag.update! end def copy_pictures(flacfile, mp3file) flacinfo = FlacInfo.new(flacfile) mp3info = ID3Lib::Tag.new(mp3file) for i in 1..flacinfo.picture["n"] # FlacInfo only supports writing the picture to a file, so we cheat a # little to get it into memory. picture_in = File.new(flacfile, "rb") picture_in.seek(flacinfo.picture[i]['raw_data_offset'], IO::SEEK_CUR) mp3info << { :id => :APIC, :mimetype => flacinfo.picture[i]["mime_type"], :picturetype => flacinfo.picture[i]["type_int"], :description => flacinfo.picture[i]["description_string"], :textenc => 0, :data => picture_in.read(flacinfo.picture[i]['raw_data_length']) } picture_in.close end mp3info.update! end dest_dir = '' opts = GetoptLong.new( [ "--destination", "-d", GetoptLong::REQUIRED_ARGUMENT ] ) opts.each do |opt, arg| case opt when "--destination" dest_dir = arg + '/' end end ARGV.each { |sourcefile| base = File.basename(sourcefile, '.flac') destfile = dest_dir + base + '.mp3' create_mp3(sourcefile, destfile) set_mp3_metadata(destfile, get_flac_metadata(sourcefile)) copy_pictures(sourcefile, destfile) }