It's a command line tool that extracts the title and performer from the MP3 files, and uses them to download the lyrics from azlyrics.com, and saves the lyrics beside the MP3, using a .txt extension.
I just needed the title and artist, so if someone wants to enhance it, there's more tags and info at http://www.id3.org/develop.html. Just check out the two first links.
:!: I've not added any code for writing tags back, as we need a more elaborate BinWrite with padding options.
And Lazlo, I think there's sort of a bug in your BinRead function - if you tell it to read 0 bytes, it loops.
I also found an error in my DeHexify function - it dropped the spaces.
Edit: And why won't the size variable show the correct total frames size?
Command line: DownloadLyrics
Example: DownloadLyrics "C:\MP3\*.mp3" Recurse Replace
Recurse=Recurse into subfolders
Replace=Replace exsting lyrics
;DownloadLyrics.ahk ; Downloads the lyrics of MP3 files ; For more tags and info on id3, check out the two first links on http://www.id3.org/develop.html ;Skrommel @2006 #SingleInstance,Force #NoEnv SetBatchLines,-1 If 0=0 { MsgBox,0,DownloadLyrics - 1 Hour Software,DownloadLyrics - Downloads the lyrics of mp3 files from azlyrics.com`n`nCommand line: `tDownloadLyrics <path to mp3(s)> [Recurse Replace]`nExample: `t`tDownloadLyrics "C:\MP3\*.mp3" Recurse Replace`n`nRecurse=Recurse into subfolders`nReplace=Replace exsting lyrics`n`nFor more tools and information, visit www.1hoursoftware.com ExitApp } path=%1% recurse=0 If (2="recurse" Or 3="recurse") recurse=1 replace=0 If (2="replace" Or 3="replace") replace=1 Loop,%path%,0,%recurse% { TrayTip,AddLyrics,%A_LoopFileLongPath% SplitPath,A_LoopFileLongPath,name,dir,ext,name_no_ext,drive file=%A_LoopFileLongPath% output=%dir%\%name_no_ext%.txt title= performer= lyrics= Gosub,READTAGS Gosub,RETRIEVE } ExitApp RETRIEVE: If title= Return If performer= Return StringReplace,performer,performer,%A_Space%,+ Loop { StringGetPos,start,performer,++ If start<0 Break StringReplace,performer,performer,++,+,All } StringReplace,title,title,%A_Space%,+,All Loop { StringGetPos,start,title,++ If start<0 Break StringReplace,title,title,++,+,All } lyricsexist=1 If lyrics<> lyricsexist=0 file=http://search.azlyrics.com/cgi-bin/azseek.cgi?q=%performer%+%title% UrlDownloadToFile,%file%,file.htm FileRead,file,file.htm StringGetPos,start,file,<b>1.</b> If start=0 Return StringGetPos,start,file,<a href=",,% start start+=10 StringGetPos,stop,file,",,% start StringMid,file,file,% start,% stop-start+1 TrayTip,AddLyrics,%file% UrlDownloadToFile,%file%,file.htm FileRead,file,file.htm StringGetPos,start,file,"</b> start+=9 StringGetPos,stop,file,`n,,% start StringMid,file,file,% start,% stop-start-2 tag=0 lyrics= Loop,Parse,file { If A_LoopField=< { tag=1 Continue } If A_LoopField=> { tag=0 Continue } If tag=0 lyrics:=lyrics A_LoopField } If (lyrics<>"" And (replace=1 or lyricsexist=0)) { FileDelete,%output% FileAppend,%lyrics%,%output% } Return READTAGS: ;;;;;;; HEADER ;;;;;;; res:=BinRead(file,data,3,0) fileidentifier:=DeHexify(data) res:=BinRead(file,data,1,3) majorversion:=data+0 res:=BinRead(file,data,1,4) revisionnumber:=data+0 version=%majorversion%.%revisionnumber% res:=BinRead(file,data,1,5) flags=0x%data% Transform,unsync,BitAnd,%flags%,256 Transform,extheader,BitAnd,%flags%,128 Transform,experimentalid,BitAnd,%flags%,64 Transform,footer,BitAnd,%flags%,32 res:=BinRead(file,data,2,8) size=0x%data% size+=0 extheaderpos:=10 ;;;;;;; EXTENDEDHEADER ;;;;;;; If extendedheader=1 { res:=BinRead(file,data,4,extheaderpos) frameid:=DeHexify(data) res:=BinRead(file,data,4,extheaderpos+4) extheadersize=0x%data% extheadersize+=10 frameheaderpos:=extheaderpos+extheadersize } Else frameheaderpos:=extheaderpos ;;;;;;; FRAMES ;;;;;;; Loop { ;;;;;;; FRAMEHEADER ;;;;;;; res:=BinRead(file,data,4,frameheaderpos) frameid:=DeHexify(data) res:=BinRead(file,data,4,frameheaderpos+4) framesize=0x%data% framesize+=0 res:=BinRead(file,data,1,frameheaderpos+8) flaga:=data res:=BinRead(file,data,1,frameheaderpos+9) flagb:=data If frameid= Break ; TIT2: title ; USLT: lyrics ; TPE1: performer If (frameid="TPE1" Or frameid="TIT2" Or frameid="USLT") { framepos:=frameheaderpos+10 ;frameheadersize=10 ;;;;;;; FRAME ;;;;;;; If framesize>0 { res:=BinRead(file,data,framesize,framepos) frametext:=DeHexify(data) If (frameid="TPE1") performer:=frametext Else If (frameid="TIT2") title:=frametext Else If (frameid="USLT") StringTrimLeft,lyrics,frametext,3 } } frameheaderpos:=frameheaderpos+framesize+10 ;frameheadersize=10 If (frameheaderpos>=size) Break } ;MsgBox,0,id3v2,Performer:%performer% `nTitle:%title% `nLyrics:`n%lyrics% Return DeHexify(x) { StringLen,len,x len:=len/2 string= Loop,%len% { StringLeft,hex,x,2 hex=0x%hex% Transform,y,Chr,%hex% string:=string y StringTrimLeft,x,x,2 } Return,string } ;By lazlo at http://www.autohotkey.com/forum/viewtopic.php?t=4546 /* ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BinWrite ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | - Open binary file | - (Over)Write n bytes (n = 0: all) | - From offset (offset < 0: counted from end) | - Close file | data -> file[offset + 0..n-1], rest of file unchanged | Return #bytes actually written */ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BinWrite(file, data, n=0, offset=0) { ; Open file for WRITE (0x40..), OPEN_ALWAYS (4): creates only if it does not exists h := DllCall("CreateFile","str",file,"Uint",0x40000000,"Uint",0,"UInt",0,"UInt",4,"Uint",0,"UInt",0) IfEqual h,-1, SetEnv, ErrorLevel, -1 IfNotEqual ErrorLevel,0,Return,0 ; couldn't create the file m = 0 ; seek to offset IfLess offset,0, SetEnv,m,2 r := DllCall("SetFilePointerEx","Uint",h,"Int64",offset,"UInt *",p,"Int",m) IfEqual r,0, SetEnv, ErrorLevel, -3 IfNotEqual ErrorLevel,0, { t = %ErrorLevel% ; save ErrorLevel to be returned DllCall("CloseHandle", "Uint", h) ErrorLevel = %t% ; return seek error Return 0 } TotalWritten = 0 m := Ceil(StrLen(data)/2) If (n <= 0 or n > m) n := m Loop %n% { StringLeft c, data, 2 ; extract next byte StringTrimLeft data, data, 2 ; remove used byte c = 0x%c% ; make it number result := DllCall("WriteFile","UInt",h,"UChar *",c,"UInt",1,"UInt *",Written,"UInt",0) TotalWritten += Written ; count written if (!result or Written < 1 or ErrorLevel) break } IfNotEqual ErrorLevel,0, SetEnv,t,%ErrorLevel% h := DllCall("CloseHandle", "Uint", h) IfEqual h,-1, SetEnv, ErrorLevel, -2 IfNotEqual t,,SetEnv, ErrorLevel, %t% Return TotalWritten } /* ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BinRead ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | - Open binary file | - Read n bytes (n = 0: all) | - From offset (offset < 0: counted from end) | - Close file | data (replaced) <- file[offset + 0..n-1] | Return #bytes actually read */ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; BinRead(file, ByRef data, n=0, offset=0) { h := DllCall("CreateFile","Str",file,"Uint",0x80000000,"Uint",3,"UInt",0,"UInt",3,"Uint",0,"UInt",0) IfEqual h,-1, SetEnv, ErrorLevel, -1 IfNotEqual ErrorLevel,0,Return,0 ; couldn't open the file m = 0 ; seek to offset IfLess offset,0, SetEnv,m,2 r := DllCall("SetFilePointerEx","Uint",h,"Int64",offset,"UInt *",p,"Int",m) IfEqual r,0, SetEnv, ErrorLevel, -3 IfNotEqual ErrorLevel,0, { t = %ErrorLevel% ; save ErrorLevel to be returned DllCall("CloseHandle", "Uint", h) ErrorLevel = %t% ; return seek error Return 0 } TotalRead = 0 data = IfEqual n,0, SetEnv n,0xffffffff ; almost infinite format = %A_FormatInteger% ; save original integer format SetFormat Integer, Hex ; for converting bytes to hex Loop %n% { result := DllCall("ReadFile","UInt",h,"UChar *",c,"UInt",1,"UInt *",Read,"UInt",0) if (!result or Read < 1 or ErrorLevel) break TotalRead += Read ; count read c += 0 ; convert to hex StringTrimLeft c, c, 2 ; remove 0x c = 0%c% ; pad left with 0 StringRight c, c, 2 ; always 2 digits data = %data%%c% ; append 2 hex digits } IfNotEqual ErrorLevel,0, SetEnv,t,%ErrorLevel% h := DllCall("CloseHandle", "Uint", h) IfEqual h,-1, SetEnv, ErrorLevel, -2 IfNotEqual t,,SetEnv, ErrorLevel, %t% SetFormat Integer, %format% ; restore original format Totalread += 0 ; convert to original format Return TotalRead }