Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Extracting MP3 tags and downloading lyrics


  • Please log in to reply
8 replies to this topic
skrommel
  • Members
  • 193 posts
  • Last active: Jun 07 2010 08:30 AM
  • Joined: 30 Jul 2004
:) Here's the start of an MP3 tags extractor.

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 [Recurse Replace]
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 
}


Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Useful script, and a great demonstration of binary file operations. Thanks for posting it.

T-nm
  • Guests
  • Last active:
  • Joined: --
EvilLyrics is pretty cool too. Give it a try, there's also a karaoke function.

kcar181
  • Guests
  • Last active:
  • Joined: --
Oh man I've been looking for something like this for a while. Sadly it just opens and closes instantly. I just discovered AutoHotkey like 30min ago so I might be doing something wrong.

I made a shortcut

"G:\Documents and Settings\Administrator\Desktop\DownloadLyrics.exe" "e:\music\*.mp3" Recurse

It then opens and then closes

Masu
  • Guests
  • Last active:
  • Joined: --
that is very interesting indeed
I'm not sure if azlyrics is the largest database, but alas,
how can one modify this so that you enter a song name, and it displays the lyrics in like a text box or so?

abc_wolf
  • Guests
  • Last active:
  • Joined: --
Would it be possible to write covers to the mp3 tags? That would be really useful for me.. Anyone has an idea?

degarb
  • Members
  • 315 posts
  • Last active: May 03 2015 07:35 PM
  • Joined: 14 Feb 2007
Any progress in this script. Still broken. Open, then closes. Same behavior when I comment out the if 0=0 lines. Probably, some great code, but a bit advanced for an average, part time, ahk scripter.

aaronbewza
  • Members
  • 466 posts
  • Last active: Feb 05 2013 08:40 AM
  • Joined: 20 Feb 2011
the most recent version of LAME v3.99.3 http://lame.sourceforge.net/
has excellent ID3V2 tagging capabilities... loads of tags are editable and it is a command-line engine.

  • Guests
  • Last active:
  • Joined: --
They closed off some experimental switches, so voice doesn't sound any good under 21 kps. With .98, you could get really good voice at about 17-19 kps.

Also this is probably useless to strip tags and retag mp3s from another application etc.

tag.exe from rarewares half the time gets flagged as a virus. Probably because it can modify another file. Regardless as to why, this making sharing your scripts with others impossible, if you need tag.exe