# AudioInfo: entrega informaci�n sobre distintos archivos de audio # Por el momento, puede manejar # * flac # * ogg # * mp3. # * shn # * ape # * soportados por audiofile : wav, aiff, snd, au #require "rubygems" gem "ruby-mp3info", ">=0.5.1" require "mp3info" require "iconv" require "taglib" require "rmac" require "vorbisfile" require "flac" require "audiofile" require "mpc" class String def encoding cuenta=0 ascii=true self.each_byte{|byte| if cuenta>0 if (byte >> 6) != 0b10 return "iso8859" else cuenta-=1 end else next if(byte<128) ascii=false if (byte >> 5) == 0b110 cuenta=1 elsif (byte >> 4) == 0b1110 cuenta=2 elsif (byte >> 3) == 0b11110 cuenta=3 else return "iso8859" end end } ascii ? "ascii":"utf8" end def to_utf8 (self.encoding=='iso8859') ? Iconv.new("utf-8","iso-8859-1").iconv(self.to_s) : self end end module AudioInfo SUPPORT=%w{flac ogg mp3 wav aiff au snd shn ape mpc} # entrega un objeto AudioInfo::Tipo def self.infoFile(sFile) # primero, identificar el tipo de archivo return false if !File.stat(sFile).file? return false if !(tipo=determinarTipoFile(sFile)) and !(tipo=determinarTipoExtension(sFile)) # ok, existe. Ahora, obtengo la informaci�n sobre �l de la manera 'legal' begin oInfoTags=self.const_get(tipo.capitalize.intern).new(sFile) rescue StandardError => ex print "Error in file #{sFile} "+ex print ex.backtrace return false end # Si falta alg�n dato, lo trato de obtener del nombre del archivo if oInfoTags.incompleto? # ahora verifico que esten todos los datos en orden. oNombre=AudioInfo::FileName.new(sFile) oInfoTags.merge(oNombre) # si todav�a falta algo, lo arreglo oInfoTags.album='Desconocido' if oInfoTags.album.nil? oInfoTags.artist='Desconocido' if oInfoTags.artist.nil? end # Si no, busco por el nombre return oInfoTags end # Determina el tipo de archivo, a partir del comando file def self.determinarTipoFile(sFile) sTipo=`file -L -b \"#{sFile}\"` if sTipo=~/^FLAC/ return 'flac' elsif sTipo=~/^MP3/ return 'mp3' elsif sTipo=~/Ogg.*Vorbis audio/ return 'ogg' end false end # Determina el tipo de archivo, a partir de su extensi�n def self.determinarTipoExtension(sFile) sSupport=SUPPORT.join('|') if sFile=~/\.(#{sSupport})$/i return $1.downcase else return false end end # Recolección de información, a partir del tipo de archivo class Tipo attr_accessor :title, :artist, :tracknumber, :album, :year, :sample_rate, :bps, :time, :bits_per_sample, :channels attr_reader :sFile, :size TAGS=['title','artist','tracknumber','album','year'] private # Aqu� se ingresan las operaciones para procesar la informaci�n def parse end public # * sFile: Nombre del archivo def initialize(sFile) @sFile=sFile @size=File.stat(@sFile).size parse #@sFile=@sFile.to_utf8 end # entrega los kbps para el archivo def kbps @bps/1024.to_f end # arregla aquellos valores de tag con formatos especiales def arreglarValor(tag,valor) valor.gsub!('_',' ') case tag when 'tracknumber' if valor=~/(\d+)\/\d+/ valor=$1 elsif valor=~/\[(\d+)\]/ valor=$1 elsif valor=~/^(\d+)/ valor=$1 end valor=nil if valor !~ /\d+/ when 'year' if valor=~/(\d+)[-\/:](\d+)[-\/:](\d+)/ valor=$3 if $3.length==4 valor=$1 if $1.length==4 elsif valor=~/(\d+)\/(\d+)/ valor=$1 elsif valor=~/(\d+)/ valor=$1 else valor="" end end valor.to_utf8 if valor.respond_to?('to_utf8') end def arreglarValoresEstandar(aInfo) aInfo.each { |tagprop,tagself| valor=yield(tagprop) valor=arreglarValor(tagself,valor.to_s) self.send(tagself+"=",valor) unless (valor.nil? or valor=="") } end # retorna true si falta alguno de los tag basicos, definidos en TAGS def incompleto? TAGS.detect {|tag| send(tag).nil?} end # recoje los valores de otro AudioInfo::Tipo y los une a los propios def merge(oInfo) raise "No es Tipo" if !oInfo.kind_of? AudioInfo::Tipo TAGS.each {|tag| tag_1=send(tag) tag_2=oInfo.send(tag) #instance_variable_set(var,tag_2) if tag_1.nil? and !tag_2.nil? send(tag+'=',tag_2) if tag_1.nil? and !tag_2.nil? } end end class Virtual < Tipo attr_reader :cd def initialize(cd,sFile) @cd=cd @sFile=sFile end end class Audiofile < Tipo def parse ::AudioFile.open(@sFile) do |file| @sample_rate=file.rate @channels=file.channels @bits_per_sample=file.bits @time=file.frame_count/file.rate.to_f end end end class Wav < Audiofile end class Aiff < Audiofile end class Au < Audiofile end class Snd < Audiofile end class Ogg < Tipo def parse aInfo={"title"=>"title","artist"=>"artist","album"=>"album", "date"=>"year", "tracknumber"=>"tracknumber"} f=File.open(@sFile, "r") vf = ::Ogg::VorbisFile.new vf.open(f) comments=vf.comments(-1) @channels=vf.channels(-1) @sample_rate=vf.sample_rate(-1) @time=vf.time_total(-1) @bps=vf.bitrate(-1) arreglarValoresEstandar(aInfo) {|tag| comments[tag]} vf.close end end class Flac < Tipo def parse aInfo={"title"=>"title","artist"=>"artist","album"=>"album", "date"=>"year", "tracknumber"=>"tracknumber"} oInfo=::Flac.info(@sFile) @time = oInfo.time @sample_rate = oInfo.sample_rate; @channels= oInfo.channels; @bits_per_sample=oInfo.bits_per_sample @bps=(@size * 8 / @time) arreglarValoresEstandar(aInfo) {|tag| oInfo.comments[tag]} end end class FileName < Tipo # Define las estructuras de nombre de archivo que pueden ser parseadas ESTRUCTURAS=[ "%a-%b-%n-%t", "%a-%y-%b/%n-%t", "%a-%y-%b/%c/%n-%t", "%a-%b/%c/%n-%t", "%a-%n-%t", "%a/%b\\(%y\\)/%n-%t", "%a/%y-%b/%n-%t", "%a/%y-%b/%n\\.%t", "%a/%y-%b/%t", "%a-%b/%n-%t", "%a-%b/%t", "%a/%n-%t", "%n-%t", "%a-%t", "%a/%t", "%t"] def parse hEquil={'%a'=>'artist','%y'=>'year','%b'=>'album','%n'=>'tracknumber', '%t'=>'title'} x=0 sFile=@sFile.gsub("_"," ") sSupport=SUPPORT.join('|') ESTRUCTURAS.each {|sTexto| aSec=sTexto.scan(/%[abcnty]/) rx=Regexp.new(".*/"+sTexto.gsub(/%[a|b|t]/, '\s*?([^/]+?)\s*?').gsub("%y", '\s*?(\d{4,4})\s*?').gsub("%n", '\s*?(\d{1,2})\.?\s*?').gsub("%c",'CD[\s-]?(\d)') + "\.(#{sSupport})$") if match=rx.match(sFile) puts "Patron:"+sTexto if $DEBUG x=1 aSec.each{|tag| valor=match[x].strip if tag!='%n' and tag!='%y' and tag!='%c' and valor=~/^\d+$/ puts "#{sFile} : Valor num�rico donde no corresponde en #{tag}:[#{valor}] #{rx}" next 2 end case tag when '%c' @album+="[CD "+valor+"]" when '%n' @tracknumber=valor.to_i when '%y' @year=valor.to_i else puts "#{sFile} : Posible error - Valor numérico donde no corresponde en #{tag}:#{valor}" if valor=~/^\d+$/ send(hEquil[tag]+'=',arreglarValor(tag,valor)) end x+=1 } break; end # fin if } end end class Mp3 < Tipo def parse aInfo={'title'=>'title', 'artist'=>'artist', 'album'=>'album', 'tracknum'=>'tracknumber','year'=>'year'} ::Mp3Info.open(@sFile) do |mp3info| @sample_rate=mp3info.samplerate @channels=2 @bits_per_sample=16 @time=mp3info.length @bps=mp3info.bitrate * 1024 arreglarValoresEstandar(aInfo) {|tag| mp3info.tag[tag]} end end end class Ape < Tipo def parse aInfo={'title'=>'title', 'artist'=>'artist', 'album'=>'album', 'track'=>'tracknumber','year'=>'year'} begin oInfo=Mac.info(@sFile) @sample_rate=oInfo.samplerate @channels=oInfo.channels @time=oInfo.length @bps=(@size*8/@time) arreglarValoresEstandar(aInfo) {|tag| oInfo.send(tag)} rescue Exception => bang puts bang.to_s end end end class Mpc < FileName def parse aInfo={'title'=>'title', 'artist'=>'artist', 'album'=>'album', 'tracknumber'=>'tracknumber','year'=>'year'} oInfo=::Mpc.info(@sFile) @sample_rate=oInfo.sample_rate @channels=oInfo.channels @time=oInfo.time @bps=oInfo.bps arreglarValoresEstandar(aInfo) {|tag| oInfo.send(tag)} end end class Shn < FileName end end