In article <0G6G004FFIMJ0S / mta5.snfc21.pbi.net>, Kevin Smith <sent / qualitycode.com> wrote: >jmichel / schur.institut.math.jussieu.fr wrote: >>point. But, since my last post, further thought has led me to a solution >>(if you like it it can be in the FAQ, Dave). The API from IO that I use >>is just for now .read and .seek but I may want to use other methods >>eventually. The solution to share code is >> >> 1- Derive a class from String that has methods .read and .seek . I say >> derive a class because I need to add an instance variable 'current >> pointer' and it seems cleaner to have this in a new class. But since >> instance variables can magically appear maybe I need not derive a >> class. I can just do: > >Perfect. And I agree that it should be a distinct >class. I would propose IOString as the name. > >Perhaps we persuade you to contribute what you >end up with to the RAA (Ruby App Archive) so >others can benefit from it, and extend it. Well, below is my code. It is a kind of 'minimal' solution: I did not derive a different class from string, and I did not even add a method as_file since .rewind does the job. Also I have yet implemented only a small subset of the ID3V2 standard, but which covers all the music files I have on my hard disk. At the bottom is a sample application where I run over all .mp3 files in a directory, show the ID3v1 and ID3v2 tags and propose to rename the file to trak_no+title.mp3 (I follow windows filename conventions, I work under windows with djgpp). Any comments on how to shorten or rubify my code are welcome. class String def rewind @current_pointer=0 end def read(n) @current_pointer+=n if @current_pointer>=length @current_pointer=length nil else self[@current_pointer-n..@current_pointer-1] end end def seek(n,where) case where when IO::SEEK_CUR @current_pointer+=n when IO::SEEK_END @current_pointer=length+n when IO::SEEK_SET @current_pointer=n end 0 end def show gsub(/[\000-\037]/,sprintf('\\\\%03o',$0.to_i)) end end class ID3V2tag attr_accessor(:size,:pad,:version,:revision,:flags,:frames) def to_s res="ID3V2."+@version.to_s+"."+@revision.to_s @flags.each_byte { |fl,i| if fl==?1 res<<"["<<%w[Unsynchronisation Extended_Header Experimental_indicator Footer_present Unknown4 Unknown5 Unknown6 Unknown7][i]<<"]" end} res+=" Size="+@size.to_s+" Padding="+pad.to_s+"\n" res<<@frames.map{|x| x.to_s}.join("\n") res end def [](tag) @frames.detect{|f| f.header==tag} end end class ID3V2Frame attr_accessor(:header,:size,:flags,:data,:encoding) def to_s desc={"TRCK"=>"Track/Elts", "TMED"=>"Media Type", "TCON"=>"Content Type", "TALB"=>"Album Title", "TPE1" =>"Lead Artist", "TIT2"=>"Title", "TYER"=>"Year", "COMM"=>"Comment", "TLEN"=>"Length", "TFLT"=>"Type"} desc.default="??" h="["+@header+"]"+desc[@header] e=case @encoding when 0 then "" # ISO-8859-1 (default) when 1 then "[UTF-16]" when 2 then "[UTF-16BE]" when 3 then "[UTF-8]" else "[UNKNOWN]" end case @header when "COMM" sprintf("%-20s<%s>",h+e+"["+@data[0..2].show+"]",@data[3.. / size-2].show) when /T.../ then res=sprintf("%-20s<%s>",h+e,@data.show) if @header=="TLEN" res+=sprintf("=%.3f sec.",@data.to_i/1000.0) elsif @header=="TFLT" res+="="+{ "MPG"=>"MPEG_Audio", "/1"=>"MPEG_1/2_layerI", "/2"=>"MPEG_1/2_layerII", "/3"=>"MPEG_1/2_layerIII", "/2.5"=>"MPEG_2.5", "/AAC"=>"Advanced_audio_compression ", "VQF"=>"Transform-domain_weighted_interleave_vector_quantisation", "PCM"=>"Pulse_code_modulated_audio"}[@data] end res else h.ljust(20)+"<"+@data.show+">" end end end module ReadID3tag def read_synchsafe_i res=0 read(4).unpack("C4").each { |b| res=128*res+b} res end def read_frame res=ID3V2Frame.new res.header=read(4) if res.header[0]==0 seek(-4,IO::SEEK_CUR) return nil end res.size=read_synchsafe_i res.flags=read(2).unpack("B8B8").join res.data=read(res.size) case res.header when /(T...)|(COMM)/ then res.encoding=res.data.slice!(0) end res end def read_ID3V2_tag rewind if read(3)!="ID3" then return nil end res=ID3V2tag.new res.version,res.revision,res.flags=read(3).unpack("CCB8") res.frames=[] res.pad=res.size=read_synchsafe_i while res.pad>=10 and (fr=read_frame) if res.pad<fr.size+10 then raise "Wrong Frame" end res.pad-=fr.size+10 res.frames<<fr end read(res.pad).each_byte{|i| if i!=0 then raise "non null byte at end" end} return res end def read_ID3V1_tag seek(-128,IO::SEEK_END) tag,title,artist,album,year,comment,track,genre= read(128).unpack("Z3Z30Z30Z30Z4Z28xC1C1") if tag!="TAG" nil else "ID3V1\nTitle=<"+title.show+">\nArtist=<"+artist.show+">\nAlbum=<"+ album.show+">\nYear="+year.show+"\nComment=<"+comment.show+ ">\nTrack="+track.to_s+"\nGenre="+genre.to_s+"\n" end end end class IO include ReadID3tag end class String include ReadID3tag end Dir["*.mp3"].each{ |f| print "\n{",f,"}\n" tag=open(f,"rb"){ |fl| print fl.read_ID3V1_tag; fl.read_ID3V2_tag} print tag,"\n" if tr=tag["TRCK"] then newname=sprintf("%02d-%s.mp3",tr.data.to_i,tag["TIT2"].data.tr( " :`\"\\/*?|","_;''--#_-")) print "propose rename (y/n/q) to\n",newname case gets when "q\n" then break when "n\n" then next when "y\n" then File.rename(f,newname) else raise "should answer q,n or y" end end }