:) But if anyone can make the Tag.exe tool work, here's AddLyrics's source code.
I've changed it to download the lyrics to the same name as the MP3, but using the .txt extension.
Remember to download Tag.exe and place it in the same folder as the script.
Skrommel
;AddLyrics.ahk
; Downloads the lyrics of MP3 files
; Uses Tag.exe from http://www.softpedia.com/get/Multimedia/Audio/Tag-Editors/Tag.shtml
;Skrommel @2006
#SingleInstance,Force
#NoEnv
SetBatchLines,-1
If 0=0
{
MsgBox,0,AddLyrics - 1 Hour Software,Command line: `tAddLyrics <path to mp3(s)> [Recurse Replace]`nExample: `t`tAddLyrics "C:\MP3\*.mp3" Recurse Replace`n`nRecurse=Recurse into subfolders`nReplace=Replace exsting lyrics`n`nwww.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
input="%A_LoopFileLongPath%"
output=%dir%\%name_no_ext%.txt
Gosub,RETRIEVE
}
ExitApp
RETRIEVE:
file:=CMDret_RunReturn("Tag.exe " input)
StringGetPos,start,file,Artist:
If start<0
Return
start+=10
StringGetPos,stop,file,`n,,% start
StringMid,artist,file,% start,% stop-start
StringReplace,artist,artist,%A_Space%,+
Loop
{
StringGetPos,start,artist,++
If start<0
Break
StringReplace,artist,artist,++,+,All
}
StringGetPos,start,file,Title:
If start<0
Return
start+=10
StringGetPos,stop,file,`n,,% start
StringMid,title,file,% start,% stop-start
StringReplace,title,title,%A_Space%,+,All
Loop
{
StringGetPos,start,title,++
If start<0
Break
StringReplace,title,title,++,+,All
}
lyricsexist=1
StringGetPos,start,file,Lyrics:
If start<0
lyricsexist=0
file=http://search.azlyrics.com/cgi-bin/azseek.cgi?q=%artist%+%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<BR><BR>,,% 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))
{
;lyrics="%lyrics%"
;file:=CMDret_RunReturn("Tag.exe " input "-t Lyrics=" lyrics)
FileDelete,%output%
FileAppend,%lyrics%,%output%
}
Return
; ******************************************************************
; CMDret-AHK functions
; version 1.08 beta
;
; Updated: March 31, 2006
; by: corrupt
; Code modifications and/or contributions made by:
; Laszlo, shimanov, toralf
; ******************************************************************
; Usage:
; CMDin - command to execute
; ******************************************************************
; Known Issues:
; - If using dir be sure to specify a path (example: cmd /c dir c:\)
; - Running 16 bit console applications may not produce output. Use
; a 32 bit application to start the 16 bit process to receive output
; ******************************************************************
; Additional requirements:
; - none
; ******************************************************************
; Code Start
; ******************************************************************
CMDret_RunReturn(CMDin)
{
Global cmdretPID
idltm := A_TickCount + 20
CMsize = 1
VarSetCapacity(CMDout, 1, 32)
VarSetCapacity(sui,68, 0)
VarSetCapacity(pi, 16, 0)
VarSetCapacity(pa, 12, 0)
Loop, 4 {
DllCall("RtlFillMemory", UInt,&pa+A_Index-1, UInt,1, UChar,12 >> 8*A_Index-8)
DllCall("RtlFillMemory", UInt,&pa+8+A_Index-1, UInt,1, UChar,1 >> 8*A_Index-8)
}
IF (DllCall("CreatePipe", "UInt*",hRead, "UInt*",hWrite, "UInt",&pa, "Int",0) <> 0) {
Loop, 4
DllCall("RtlFillMemory", UInt,&sui+A_Index-1, UInt,1, UChar,68 >> 8*A_Index-8)
DllCall("GetStartupInfo", "UInt", &sui)
Loop, 4 {
DllCall("RtlFillMemory", UInt,&sui+44+A_Index-1, UInt,1, UChar,257 >> 8*A_Index-8)
DllCall("RtlFillMemory", UInt,&sui+60+A_Index-1, UInt,1, UChar,hWrite >> 8*A_Index-8)
DllCall("RtlFillMemory", UInt,&sui+64+A_Index-1, UInt,1, UChar,hWrite >> 8*A_Index-8)
DllCall("RtlFillMemory", UInt,&sui+48+A_Index-1, UInt,1, UChar,0 >> 8*A_Index-8)
}
IF (DllCall("CreateProcess", Int,0, Str,CMDin, Int,0, Int,0, Int,1, "UInt",0, Int,0, Int,0, UInt,&sui, UInt,&pi) <> 0) {
Loop, 4
cmdretPID += *(&pi+8+A_Index-1) << 8*A_Index-8
Loop {
idltm2 := A_TickCount - idltm
If (idltm2 < 10) {
DllCall("Sleep", Int, 10)
Continue
}
IF (DllCall("PeekNamedPipe", "uint", hRead, "uint", 0, "uint", 0, "uint", 0, "uint*", bSize, "uint", 0 ) <> 0 ) {
Process, Exist, %cmdretPID%
IF (ErrorLevel OR bSize > 0) {
IF (bSize > 0) {
VarSetCapacity(lpBuffer, bSize+1)
IF (DllCall("ReadFile", "UInt",hRead, "Str", lpBuffer, "Int",bSize, "UInt*",bRead, "Int",0) > 0) {
IF (bRead > 0) {
TRead += bRead
VarSetCapacity(CMcpy, (bRead+CMsize+1), 0)
CMcpy = a
DllCall("RtlMoveMemory", "UInt", &CMcpy, "UInt", &CMDout, "Int", CMsize)
DllCall("RtlMoveMemory", "UInt", &CMcpy+CMsize, "UInt", &lpBuffer, "Int", bRead)
CMsize += bRead
VarSetCapacity(CMDout, (CMsize + 1), 0)
CMDout=a
DllCall("RtlMoveMemory", "UInt", &CMDout, "UInt", &CMcpy, "Int", CMsize)
}
}
}
}
ELSE
break
}
ELSE
break
idltm := A_TickCount
}
}
cmdretPID=
DllCall("CloseHandle", UInt, hWrite)
DllCall("CloseHandle", UInt, hRead)
}
IF (StrLen(CMDout) < TRead) {
VarSetCapacity(CMcpy, TRead, 32)
TRead2 = %TRead%
Loop {
DllCall("RtlZeroMemory", "UInt", &CMcpy, Int, TRead)
NULLptr := StrLen(CMDout)
cpsize := Tread - NULLptr
DllCall("RtlMoveMemory", "UInt", &CMcpy, "UInt", (&CMDout + NULLptr + 2), "Int", (cpsize - 1))
DllCall("RtlZeroMemory", "UInt", (&CMDout + NULLptr), Int, cpsize)
DllCall("RtlMoveMemory", "UInt", (&CMDout + NULLptr), "UInt", &CMcpy, "Int", cpsize)
TRead2 --
IF (StrLen(CMDout) > TRead2)
break
}
}
StringTrimLeft, CMDout, CMDout, 1
Return, CMDout
}
:) Here's another, called DownloadLyrics, without the dependency of Tag.exe
To make it work, save the text below as DownloadLyrics.ahk, then make a shortcut to it, rightclick the shortcut and select properties, and add the text after the command line, so it looks something like this:
"C:\DownloadLyrics\DownloadLyrics.ahk" "D:\*.mp3" Recurse Overwrite
Skrommel
;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,DownloadLyrics,%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,DownloadLyrics,%file%
UrlDownloadToFile,%file%,file.htm
FileRead,file,file.htm
StringGetPos,start,file,"</b>
start+=9
StringGetPos,stop,file,`n<BR><BR>,,% 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
}