ID3 V1 and V2 Tags with Pictures and Lyrics

Share your advanced PureBasic knowledge/code with the community.
localmotion34
Enthusiast
Enthusiast
Posts: 665
Joined: Fri Sep 12, 2003 10:40 pm
Location: Tallahassee, Florida

ID3 V1 and V2 Tags with Pictures and Lyrics

Post by localmotion34 »

Ok, 90% of the credit goes to a14xerus on the German forum for the CORRECT ID3 V2 way of reading frames. Reading a long followed by bitshifting with "8" rather than "7" solves ALL problems with Private, APIC, and USLT frames.

This example is a GUI that allows multiple selection of MP3 files, a single MP3 file, and will read the picture and lyrics. Clicking on the listicon reads the ID3 APIC and USLT frames, and of they don't exist, does nothing.

I have implemented lower level functions BASED on a14xerus' routines that allow direct extracting of pictures, picture data, and lyrics. NOTE: if you get the binary image data with ID3GetImageBinaryData(), the picture remains COMPRESSED, and is NOT decoded. ID3GetImage returns a PB image#.

These lower level functions allow extraction of the actual frame data without any manipulation in case anyone wants to.

I have tested 1476 MP3s of mine which have been tagged with TagRunner, MediaMonkey, Tag and Rename, Ultra Tag Editor, and Zortam. Not a single crash reported.

Just a quick note: The GiveawayofTheDay version of Zortam has a NASTY bug in it where if you kept the serial and reinstalled the GAOTD version and re-registered, it thinks it is pirated, and will add adjust the APIC, USLT, or PRIV frame size to be incorrect, and crash the crap out of the reader. I have included a small check for this, it appears to work.

If ANYONE can help make these BULLETPROOF or OPTIMIZE, id really really really appreciate it.

Code: Select all

; ID3 Example for PB 4.xx
; Coder: 'a14xerus' (http://www.alexander-n.de)
; with friendly support of 'Padde'
; 06.06.2007
; Reference for ID3v2: http://www.id3.org/id3v2.3.0
;EnableExplicit


UseJPEGImageDecoder()
UsePNGImageDecoder()
;- Constants 
#ID3_BinaryReturn=0
#ID3_ImageReturn=1

;- Structures
Structure TagV1 ; Only a few Tags, a list of all tags on http://www.id3.org
  tag.s
  title.s     ; "TIT2"
  artist.s    ; "TPE1"
  album.s     ; "TALB"
  year.s      ; ...
  genre.s
  URL.s
  Copyright.s
  track.s
EndStructure

Structure TagV2 ; Only a few Tags, a list of all tags on http://www.id3.org
  title.s     ; "TIT2"
  artist.s    ; "TPE1"
  album.s     ; "TALB"
  year.s      ; ...
  genre.s
  URL.s
  Copyright.s
  track.s
  lyrics.s
  image.l
EndStructure

Global Tags.TagV2
Global TagV1.TagV1


;- ID3 Tag Version 1 Read
Procedure GetID3v1Tag(filename.s,*infos.TagV1)
  Protected *mem, header$, Result.l
  If ReadFile(0,filename)
    *mem =  AllocateMemory(128) ; allocate 128 byte
    If *mem
      FileSeek(0,Lof(0)-128)
      ReadData(0,*mem , 128)    ; read the last 128 byte
      header$ = PeekS(*mem , 3)
      If header$ = "TAG"                              ;  3 chars
        With *infos
          \title     = Trim(PeekS(*mem  +   3, 30))   ; 30 chars
          \artist    = Trim(PeekS(*mem  +  33, 30))   ; 30 chars
          \album     = Trim(PeekS(*mem  +  63, 30))   ; 30 chars
          \year      = Trim(PeekS(*mem  +  93,  4))   ;  4 chars
          ; \Comment   = Trim(PeekS(*mem  +  97, 29))   ; 30 chars
          \track     = Trim(PeekS(*mem  + 126,  1))   ;  1 chars
          \genre     = Trim(PeekS(*mem  + 127,  1))   ;  1 chars
        EndWith
        Result = #True
      EndIf
      FreeMemory(*mem)
    EndIf
    CloseFile(0)
  EndIf
  ProcedureReturn Result
EndProcedure

;- ID3 Tag Version 2 Procedures 
Procedure frameTXXX(id.s, FrameSize.l, *infos.TagV2)
  Protected TextEncoding.b, *mem, Contents.s
  
  TextEncoding = ReadByte(0)&$FF ; TXXX Textencoding
  FrameSize - 1 ; subtract TextEncoding-Byte from size
  If FrameSize <= 0
    ProcedureReturn #False
  EndIf
  *mem = AllocateMemory(FrameSize)
  ReadData(0,*mem, FrameSize)
  If TextEncoding = 0
    Contents = PeekS(*mem,FrameSize,#PB_Ascii)
  ElseIf TextEncoding = 1
    Contents = PeekS(*mem+2,FrameSize-2,#PB_UTF16)
  EndIf
  FreeMemory(*mem)
  
  With *infos
    Select id
      Case "TIT2"
        \title = Contents
      Case "TPE1", "TPE1"
        \artist = Contents
      Case "TALB"
        \album = Contents
      Case "TYER"
        \year = Contents
      Case "TCON"
        \genre = Contents
      Case "TCOP"
        \Copyright = Contents
      Case "TRCK"
        \track = Contents
    EndSelect
  EndWith
  ProcedureReturn #True
EndProcedure

Procedure frameWXXX(id.s, FrameSize.l, *infos.TagV2)
  Protected Contents.s
  ReadByte(0) ; WXXX Textencoding for Description (URLs are allways ASCII)
  FrameSize - 1
  If FrameSize <= 0
    ProcedureReturn #False
  EndIf
  Contents = Space(FrameSize)
  ReadData(0,@Contents, FrameSize)
  *infos\URL = Contents
  ProcedureReturn #True
EndProcedure

Procedure frameAPIC(id.s, FrameSize.l, *infos.TagV2)
  Protected TextEncoding.b, tmp.l, *mem
  tmp = Loc(0)
  TextEncoding = ReadByte(0) ; APIC Textencoding
  If TextEncoding = 0
    ReadString(0,#PB_Ascii) ; APIC MIME (ASCII)
  ElseIf TextEncoding = 1
    ReadString(0,#PB_UTF16) ; APIC MIME (UTF16 / Unicode)
  EndIf
  If ReadByte(0)&$FF = $03 ; APIC Picture Typ ($03 = Cover front) (for overview look at http://www.id3.org)
    If TextEncoding = 0
      ReadString(0,#PB_Ascii) ; APIC Description (ASCII)
    ElseIf TextEncoding = 1
      ReadString(0,#PB_UTF16) ; APIC Description (UTF16)
    EndIf
    FrameSize - (Loc(0)-tmp) ; subtract the desciptions from the framesize to get the picturesize
    *mem = AllocateMemory(FrameSize)
    ReadData(0,*mem, FrameSize)
    *infos\image = CatchImage(-1,*mem,FrameSize,#PB_Image_DisplayFormat)
    FreeMemory(*mem)
  EndIf
EndProcedure

Procedure FrameUSLT(id.s, FrameSize.l, *infos.TagV2) ; Hacked together by Localmotion34 - Props to a14xerus for finding Framseize code that WORKS 
  Protected TextEncoding.b, tmp.l, *mem
  tmp = Loc(0)
  TextEncoding = ReadByte(0) ; USLT Textencoding
  If TextEncoding = 0
    ReadString(0,#PB_Ascii) ; APIC MIME (ASCII)
  ElseIf TextEncoding = 1
    ReadString(0,#PB_UTF16) ; APIC MIME (UTF16 / Unicode)
  EndIf
  
  FrameSize -(Loc(0)-tmp) ; subtract the desciptions from the framesize to get the picturesize
  If FrameSize>0
    *mem = AllocateMemory(FrameSize)
    ReadData(0,*mem, FrameSize)
    *infos\lyrics = PeekS(*mem,FrameSize)
    FreeMemory(*mem)
  Else
    *infos\lyrics =""
  EndIf 
EndProcedure 

Procedure GetID3v2Tag(filename.s,*infos.TagV2)
  Protected ID3.s, ID3Size, byte.l, Size.l, FrameID.s, FrameSize.l, Location.l
  If ReadFile(0,filename)
    ID3.s = Space(3)
    ReadData(0,@ID3, 3)
    If ID3 = "ID3" And ReadByte(0) = $03 ; must be ID3v2.3.x !
      ReadByte(0) ; Revision  (not needed)
      ReadByte(0) ; Flags     (not needed)
      ID3Size = 0             ; get hole Size of ID3Tag
      ID3Size = ReadLong(0)
      ID3Size = ((ID3Size&$FF)<<24)+((ID3Size&$FF00)<<8)+((ID3Size&$FF0000)>>8)+((ID3Size>>24)&$FF)
    Else
      ; no ID3v2.3.x tag present
      CloseFile(0)
      ProcedureReturn #False
    EndIf
    
    Size.l = 0
    Repeat
      ; examine All frames
      
      ; / Frameheader starts
      FrameID.s = Space(4)
      ReadData(0,@FrameID, 4)   ; Read FrameID (allways 4 chars)
      
      If Asc(Left(FrameID, 1)) = 0
        CloseFile(0)
        ProcedureReturn #True
      EndIf
      
      ;;; F*ck you Id3lib.org.  your Sh*t documentation makes it IMPOSSIBLE to get correct frame sizes
      FrameSize = 0
      FrameSize = ReadLong(0)   ; get framesize ( the framesize is the size of the values excluding the frameheader)
      FrameSize = ((FrameSize&$FF)<<24)+((FrameSize&$FF00)<<8)+((FrameSize&$FF0000)>>8)+((FrameSize>>24)&$FF) ; props to a14xerus 
      Size + FrameSize
      
      ReadByte(0): ReadByte(0) ; Frame Flags (not needed)
      
      If FrameSize < 1 :  ProcedureReturn #False : EndIf
      
      ; \ Frameheader ends (Frameheader allways 10 Bytes)
      
      Location = Loc(0) + FrameSize ; set 'location'value to end of the actual frame
      
      ; / Framebody starts
      
      Select FrameID  ; Read teh FrameID (Overview on http://www.id3.org)
        Case "TIT2", "TPE1", "TALB", "TYER", "TCON", "TCOP", "TRCK", "TPE2"
          frameTXXX(FrameID, FrameSize, *infos)
        Case "WXXX"
          frameWXXX(FrameID, FrameSize, *infos)
        Case "APIC"
          frameAPIC(FrameID, FrameSize, *infos)
        Case "USLT"
          FrameUSLT(FrameID, FrameSize, *infos)
          ; Debug "found lyrics" 
          ; Debug FrameSize 
      EndSelect
      
      ; \ Framebody ends
      
      FileSeek(0,Location) ; Jump to the end of the frame.
      ; (if something went wrong nevertheless you are at the right location in the file)
      
    Until Size >= ID3Size ; stop if tag size reached/exceeded
    CloseFile(0)
  EndIf
EndProcedure
 

Procedure GetID3Tag(filename.s,*infos.TagV1, *info.TagV2)
  With *info ; delete the old settings
    \album = ""
    \artist = ""
    \Copyright = ""
    \genre = ""
    \title = ""
    \track = ""
    \URL = ""
    \year = "" 
    \lyrics=""
    If \image
      FreeImage(\image)
    EndIf
    \image = 0
  EndWith
  
  If Not filename
    ProcedureReturn #False
  EndIf
  GetID3v1Tag(filename,*infos) ; if there are old ID3v1 Tags, read them out
  GetID3v2Tag(filename,*info) ; read the new Version (ID3v2)
EndProcedure
 
;- Lower Level Procedures for ID3 Tag Version 2 Reading
Procedure frameAPICReturn(id.s, FrameSize.l, *infos.TagV2, BinaryOrImage)
  Protected TextEncoding.b, tmp.l, *mem
  tmp = Loc(0)
  TextEncoding = ReadByte(0) ; APIC Textencoding
  If TextEncoding = 0
    ReadString(0,#PB_Ascii) ; APIC MIME (ASCII)
  ElseIf TextEncoding = 1
    ReadString(0,#PB_UTF16) ; APIC MIME (UTF16 / Unicode)
  EndIf
  If ReadByte(0)&$FF = $03 ; APIC Picture Typ ($03 = Cover front) (for overview look at http://www.id3.org)
    If TextEncoding = 0
      ReadString(0,#PB_Ascii) ; APIC Description (ASCII)
    ElseIf TextEncoding = 1
      ReadString(0,#PB_UTF16) ; APIC Description (UTF16)
    EndIf
    FrameSize - (Loc(0)-tmp) ; subtract the desciptions from the framesize to get the picturesize
    *mem = AllocateMemory(FrameSize)
    ReadData(0,*mem, FrameSize)
    Select BinaryOrImage
      Case 0
        ProcedureReturn *mem 
      Case 1
        *infos\image = CatchImage(-1,*mem,FrameSize,#PB_Image_DisplayFormat)
        FreeMemory(*mem)
        ProcedureReturn *infos\image
    EndSelect 
  EndIf
EndProcedure

Procedure ID3GetImage(filename.s) ; Returns a HBITMAP
  Protected ID3.s, ID3Size, byte.l, Size.l, FrameID.s, FrameSize.l, Location.l, *infos.TagV2 
  If ReadFile(0,filename)
    ID3.s = Space(3)
    ReadData(0,@ID3, 3)
    If ID3 = "ID3" And ReadByte(0) = $03 ; must be ID3v2.3.x !
      ReadByte(0) ; Revision  (not needed)
      ReadByte(0) ; Flags     (not needed)
      ID3Size = 0             ; get hole Size of ID3Tag
      ID3Size = ReadLong(0)
      ID3Size = ((ID3Size&$FF)<<24)+((ID3Size&$FF00)<<8)+((ID3Size&$FF0000)>>8)+((ID3Size>>24)&$FF)
    Else
      ; no ID3v2.3.x tag present
      CloseFile(0)
      ProcedureReturn #False
    EndIf
    *infos.TagV2=AllocateMemory(SizeOf(TagV2))
    Size.l = 0
    Repeat
      ; examine All frames
      
      ; / Frameheader starts
      FrameID.s = Space(4)
      ReadData(0,@FrameID, 4)   ; Read FrameID (allways 4 chars)
      
      If Asc(Left(FrameID, 1)) = 0
        CloseFile(0)
        ProcedureReturn #True
      EndIf
      
      FrameSize = 0
      FrameSize = ReadLong(0)   ; get framesize ( the framesize is the size of the values excluding the frameheader)
      FrameSize = ((FrameSize&$FF)<<24)+((FrameSize&$FF00)<<8)+((FrameSize&$FF0000)>>8)+((FrameSize>>24)&$FF) ; props to a14xerus 
      Size + FrameSize
      
      ReadByte(0): ReadByte(0) ; Frame Flags (not needed)
      
      If FrameSize < 1 :  ProcedureReturn #False : EndIf
      
      ; \ Frameheader ends (Frameheader allways 10 Bytes)
      
      Location = Loc(0) + FrameSize ; set 'location'value to end of the actual frame
      
      ; / Framebody starts
      Select FrameID 
        Case "APIC"
          returnimage.l=frameAPICReturn(FrameID, FrameSize, *infos, #ID3_ImageReturn)
          Break 
      EndSelect
      
      ; \ Framebody ends
      
      FileSeek(0,Location) ; Jump to the end of the frame.
      ; (if something went wrong nevertheless you are at the right location in the file)
    Until Size >= ID3Size ; stop if tag size reached/exceeded
    CloseFile(0)
    FreeMemory(*infos) 
    ProcedureReturn returnimage 
  EndIf 
EndProcedure 

Procedure ID3GetImageBinaryData(filename.s);Returns compressed image data--YOU have to decode it and/or write it to a file!!!!!!
  Protected ID3.s, ID3Size, byte.l, Size.l, FrameID.s, FrameSize.l, Location.l
  If ReadFile(0,filename)
    ID3.s = Space(3)
    ReadData(0,@ID3, 3)
    If ID3 = "ID3" And ReadByte(0) = $03 ; must be ID3v2.3.x !
      ReadByte(0) ; Revision  (not needed)
      ReadByte(0) ; Flags     (not needed)
      ID3Size = 0             ; get hole Size of ID3Tag
      ID3Size = ReadLong(0)
      ID3Size = ((ID3Size&$FF)<<24)+((ID3Size&$FF00)<<8)+((ID3Size&$FF0000)>>8)+((ID3Size>>24)&$FF)
    Else
      ; no ID3v2.3.x tag present
      CloseFile(0)
      ProcedureReturn #False
    EndIf
    *infos.TagV2=AllocateMemory(SizeOf(TagV2))
    Size.l = 0
    Repeat
      ; examine All frames
      
      ; / Frameheader starts
      FrameID.s = Space(4)
      ReadData(0,@FrameID, 4)   ; Read FrameID (allways 4 chars)
      
      If Asc(Left(FrameID, 1)) = 0
        CloseFile(0)
        ProcedureReturn #True
      EndIf
      
      FrameSize = 0
      FrameSize = ReadLong(0)   ; get framesize ( the framesize is the size of the values excluding the frameheader)
      FrameSize = ((FrameSize&$FF)<<24)+((FrameSize&$FF00)<<8)+((FrameSize&$FF0000)>>8)+((FrameSize>>24)&$FF) ; props to a14xerus 
      Size + FrameSize
      
      ReadByte(0): ReadByte(0) ; Frame Flags (not needed)
      
      If FrameSize < 1 :  ProcedureReturn #False : EndIf
      
      ; \ Frameheader ends (Frameheader allways 10 Bytes)
      
      Location = Loc(0) + FrameSize ; set 'location'value to end of the actual frame
      
      ; / Framebody starts
      Select FrameID 
        Case "APIC"
          imagemem.l=frameAPICReturn(FrameID, FrameSize, *infos, #ID3_BinaryReturn)
          Break 
      EndSelect
      
      ; \ Framebody ends
      
      FileSeek(0,Location) ; Jump to the end of the frame.
      ; (if something went wrong nevertheless you are at the right location in the file)
    Until Size >= ID3Size ; stop if tag size reached/exceeded
    CloseFile(0)
    FreeMemory(*infos) 
    ProcedureReturn imagemem
  EndIf 
EndProcedure 

Procedure.s FrameUSLTreturn(id.s, FrameSize.l, *infos.TagV2) ; Hacked together by Localmotion34 - Props to a14xerus for finding Framseize code that WORKS 
  Protected TextEncoding.b, tmp.l, *mem
  tmp = Loc(0)
  TextEncoding = ReadByte(0) ; USLT Textencoding
  If TextEncoding = 0
    ReadString(0,#PB_Ascii) ; APIC MIME (ASCII)
  ElseIf TextEncoding = 1
    ReadString(0,#PB_UTF16) ; APIC MIME (UTF16 / Unicode)
  EndIf
  
  FrameSize -(Loc(0)-tmp) ; subtract the desciptions from the framesize to get the picturesize
  If FrameSize>0
    *mem = AllocateMemory(FrameSize)
    ReadData(0,*mem, FrameSize)
    *infos\lyrics = PeekS(*mem,FrameSize)
    FreeMemory(*mem)
    ProcedureReturn *infos\lyrics
  Else
    *infos\lyrics =""
    ProcedureReturn ""
  EndIf 
EndProcedure 

Procedure.s ID3GetSongLyrics(filename.s)
  Protected ID3.s, ID3Size, byte.l, Size.l, FrameID.s, FrameSize.l, Location.l, *infos.TagV2 
  If ReadFile(0,filename)
    ID3.s = Space(3)
    ReadData(0,@ID3, 3)
    If ID3 = "ID3" And ReadByte(0) = $03 ; must be ID3v2.3.x !
      ReadByte(0) ; Revision  (not needed)
      ReadByte(0) ; Flags     (not needed)
      ID3Size = 0             ; get hole Size of ID3Tag
      ID3Size = ReadLong(0)
      ID3Size = ((ID3Size&$FF)<<24)+((ID3Size&$FF00)<<8)+((ID3Size&$FF0000)>>8)+((ID3Size>>24)&$FF)
    Else
      ; no ID3v2.3.x tag present
      CloseFile(0)
      ProcedureReturn ""
    EndIf
    *infos.TagV2=AllocateMemory(SizeOf(TagV2))
    Size.l = 0
    Repeat
      ; examine All frames
      
      ; / Frameheader starts
      FrameID.s = Space(4)
      ReadData(0,@FrameID, 4)   ; Read FrameID (allways 4 chars)
      
      If Asc(Left(FrameID, 1)) = 0
        CloseFile(0)
        ;ProcedureReturn #True
      EndIf
      
      FrameSize = 0
      FrameSize = ReadLong(0)   ; get framesize ( the framesize is the size of the values excluding the frameheader)
      FrameSize = ((FrameSize&$FF)<<24)+((FrameSize&$FF00)<<8)+((FrameSize&$FF0000)>>8)+((FrameSize>>24)&$FF) ; props to a14xerus 
      Size + FrameSize
      
      ReadByte(0): ReadByte(0) ; Frame Flags (not needed)
      
      If FrameSize < 1 :  ProcedureReturn "" : EndIf
      
      ; \ Frameheader ends (Frameheader allways 10 Bytes)
      
      Location = Loc(0) + FrameSize ; set 'location'value to end of the actual frame
      
      ; / Framebody starts
      Select FrameID 
        Case "USLT"
          returntext.s=FrameUSLTreturn(FrameID, FrameSize, *infos)
          Break 
      EndSelect
      
      ; \ Framebody ends
      
      FileSeek(0,Location) ; Jump to the end of the frame.
      ; (if something went wrong nevertheless you are at the right location in the file)
    Until Size >= ID3Size ; stop if tag size reached/exceeded
    CloseFile(0)
    FreeMemory(*infos) 
    If Len(returntext)>0
      ProcedureReturn returntext 
    Else
      ProcedureReturn ""
    EndIf 
  EndIf 
EndProcedure 

;- DEMO Useage 
If OpenWindow(0, 259, 217, 855, 388, "ID3 Tags",  #PB_Window_SystemMenu |  #PB_Window_TitleBar | #PB_Window_ScreenCentered )
  If CreateGadgetList(WindowID(0))
    ListIconGadget(20,5,40,200,300,"MP3 File",180,#PB_ListIcon_GridLines|#PB_ListIcon_FullRowSelect|#PB_ListIcon_AlwaysShowSelection)
    hwnd = ImageGadget(40,220,40,300,300,0,#PB_Image_Border) ; AKJ
    ButtonGadget(1,240,10,90,25,"Open MP3") ; AKJ Cater for large fonts
    ButtonGadget(2,40,10,90,25,"Select Files")
    ButtonGadget(3,140,10,90,25,"Get ID3 Image")
    ButtonGadget(4,340,10,140,25,"Extract Selected ID3 Image")
    ButtonGadget(5,490,10,140,25,"Extract Selected Lyrics")
    EditorGadget(50,530,40,300,300,#PB_Editor_ReadOnly)
  EndIf
EndIf

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_Gadget
      Select EventGadget()
        Case 1 ; Button gadget
          MP3File.s = OpenFileRequester("Select MP3 File", "", "MP3 Files (*.mp3)|*.mp3", 0)
          If MP3File
            GetID3Tag(MP3File,@TagV1,@Tags)
            With Tags
              Debug "Album: "+\album
              Debug "Artist: "+\artist
              Debug "Copyright: "+\Copyright
              Debug "Genre: "+\genre
              Debug "Title: "+\title
              Debug "Track: "+\track
              Debug "URL: "+\URL
              Debug "Year: "+\year
              Debug \lyrics 
            EndWith
            If Tags\image
              ResizeImage(Tags\image,300,300,#PB_Image_Smooth)
              SetGadgetState(40,ImageID(Tags\image))
            Else 
              SetGadgetState(40,0)
            EndIf 
            If Tags\lyrics
              SetGadgetText(50,Tags\lyrics)
            Else
              SetGadgetText(50,"")
            EndIf 
          EndIf 
        Case 2
          mp3files$= OpenFileRequester("Select MP3 File", "", "MP3 Files (*.mp3)|*.mp3", 0,#PB_Requester_MultiSelection)
          If mp3files$
            ClearGadgetItemList(20)
            While mp3files$  
              mp3files$ = NextSelectedFileName() 
              AddGadgetItem(20,-1,mp3files$)
            Wend 
            
          EndIf 
          
        Case 3
          MP3File.s = OpenFileRequester("Select MP3 File", "", "MP3 Files (*.mp3)|*.mp3", 0)
          pbimage=ID3GetImage(MP3File)
          If pbimage
            ResizeImage(pbimage,300,300,#PB_Image_Smooth)
            SetGadgetState(40,ImageID(pbimage))
          EndIf 
        Case 4
          JPEGFile.s = SaveFileRequester("Save ID3 Tag Image To File", "", "JPEG Files (*.jpg)|*.jpg", 0)
          If JPEGFile
            If GetExtensionPart(JPEGFile)<> LCase("jpg")
              JPEGFile+".jpg"
            EndIf 
            MP3File.s=GetGadgetItemText(20,GetGadgetState(20),0)
            If FileSize(MP3File)>0
              memorybuffer=ID3GetImageBinaryData(MP3File)
              If CreateFile(10,JPEGFile)
                WriteData(10,memorybuffer, MemorySize(memorybuffer))
                CloseFile(10)
              EndIf 
            EndIf 
          EndIf 
          
        Case 5
          textFile.s = SaveFileRequester("Save ID3 Lyrics To File", "", "Text Files (*.txt)|*.txt", 0)
          If textFile
            If GetExtensionPart(textFile)<> LCase("txt")
              textFile+".txt"
            EndIf 
            MP3File.s=GetGadgetItemText(20,GetGadgetState(20),0)
            If FileSize(MP3File)>0
              text.s=ID3GetSongLyrics(MP3File)
              If Len(text)>0
                If CreateFile(10,textFile)
                  WriteData(10,@text, Len(text))
                  CloseFile(10)
                EndIf 
              EndIf 
            EndIf 
          EndIf 
          
        Case 20
          MP3File.s=GetGadgetItemText(20,GetGadgetState(20),0)
          GetID3Tag(MP3File,@TagV1,@Tags)
          If Tags\image
            ResizeImage(Tags\image,300,300,#PB_Image_Smooth)
            SetGadgetState(40,ImageID(Tags\image))
          Else
            SetGadgetState(40,0)
          EndIf 
          If Tags\lyrics
            SetGadgetText(50,Tags\lyrics)
          Else 
            SetGadgetText(50,"")
          EndIf 
      EndSelect
    Case #PB_Event_Menu
      Select EventMenu()
      EndSelect
      
    Case #PB_Event_CloseWindow
      Quit=1
  EndSelect
Until Quit=1

 

Code: Select all

!.WHILE status != dwPassedOut
! Invoke AllocateDrink, dwBeerAmount
!MOV Mug, Beer
!Invoke Drink, Mug, dwBeerAmount
!.endw
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

You probably know it, but without unicode support it doesn't show unicode characters successfully. That makes it fail on a lot of my tracks.

Additionally, on this track it doesn't seem to work at all (not even showing garbage for unicode): http://willhostforfood.com/dl.php?fileid=15981
Tranquil
Addict
Addict
Posts: 952
Joined: Mon Apr 28, 2003 2:22 pm
Location: Europe

Post by Tranquil »

This code doesnt seem to be really correct. It has problems reading unicode strings even if it is coded.

The ID3 Tag header can not be read if PB is in unicode mode.
This line makes trubble:

Code: Select all

ID3.s = Space(3)
Ive replaced it with

Code: Select all

ReadData(0,*HeaderBuffer, 3)
    ID3.s = PeekS(*HeaderBuffer,3,#PB_Ascii)
    FreeMemory(*HeaderBuffer)
Which then identifies the ID3 Tag header correctly.

EDIT:

The following lines needs to be changes too, to be able to work in PB Unicode mode:

Code: Select all

      FrameID.s = Space(4)
Changed to:

Code: Select all

      *FrameIDBuff=AllocateMemory(4)
      ReadData(0,*FrameIDBuff, 4)   ; Read FrameID (allways 4 chars)
      FrameID.s = PeekS(*FRameIDBuff,4,#PB_Ascii)
      FreeMemory(*FrameIDBuff)
... and frames will be recognized again. If I now use a MessageRequester for displaying the frame contents, I get the correct frame content of the Mp3 (same as displayed in winamp) BUT with a little trashi datas attached. (Maybe its my missing font!?) I tried Tronds Mp3.

Maybe Trond can give it a try now. Dont forget to enable unicode. :)
Tranquil
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

It has trashed data attached now, even for non-unicode tags.
localmotion34
Enthusiast
Enthusiast
Posts: 665
Joined: Fri Sep 12, 2003 10:40 pm
Location: Tallahassee, Florida

Post by localmotion34 »

Trond wrote:It has trashed data attached now, even for non-unicode tags.
My code does? or the MP3? I don't quite understand. Ugh, I hate unicode, but if this code is going to work, I have to resolve the root of the problem.

I can't even get ID3lib to properly work on my MP3s, so I am wondering if i modified this code to deal with tags written by Delphi libs or other libraries that don't conform to the standard.

Tag& Rename is Delphi I think, but i am not sure about TagRunner. Tagrunner will download all the data for untagged MP3s and write them.

I have sourcecode for an ID3 DLL written in Goasm by Donkey, but it completely fails if there are private tags, yet it will extract the ID3 image on EVERY track perfectly.

I will shortly post the GoASM DLL source and Radasm project, and my implementation of the ID3lib header, and maybe we can work out a way to correctly do this.


EDIT:

here is the link for all 3 projects. You need RADAsm and GoASM to compile Donkey's MP3Tags.dll

http://www.penguinbyte.com/apps/pbwebst ... roject.rar

Code: Select all

!.WHILE status != dwPassedOut
! Invoke AllocateDrink, dwBeerAmount
!MOV Mug, Beer
!Invoke Drink, Mug, dwBeerAmount
!.endw
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

My code should work (see here: http://www.purebasic.fr/english/viewtop ... hlight=id3), but it relies on Windows Media SDK, which isn't 100% elegant. Also I have no idea to make it extract images, but you could probably do that.

So don't fix this code for me (as I use the Windows Media lib), I just wanted to point out that it's not 100% perfect since it doesn't deal with unicode.
User avatar
Michael Vogel
Addict
Addict
Posts: 2797
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Post by Michael Vogel »

Thanks localmotion34 for sharing the code - I like things running without .net, libraries and extra DLLs!

Maybe I will have some time in one or two month to write a small viewer plugin for the MediaMonkey which just displays the cover and some informations about the actual song...

I just started some weeks ago with the mirror effect all know from iTunes - maybe someone likes to use/improve that code. It loads two files, "CoverCase.png" which has an highlighted area and the "Cover.bmp" - if needed, I'll post it somewhere on a server...

Code: Select all

; Define

	Global CoverSize=300; 		Size of CD-Cover
	Global MirrorHeight=200;	Height of Mirror
	#CompressMirror=1;			Resize Mirror
	#WhiteBackground=0;		Black or White?

	Enumeration 700
		#ImageCover
		#ImageMirror
		#ImageCase
	EndEnumeration

	If #WhiteBackground
		Global ColorBackground=#White
		Global ColorStart=32
		Global ColorEnd=224
	Else
		Global ColorBackground=#Black
		Global ColorStart=-32
		Global ColorEnd=-224
	EndIf

	Global WinID
	Global ImageID
	Global ShadowID
	Global GlossyID

	UsePNGImageDecoder()

; EndDefine

Procedure.c ScaleByte(a,pos)
	a+ColorStart
	a+(pos*ColorEnd)>>8
	If a<0
		ProcedureReturn 0
	EndIf
	If a>255
		ProcedureReturn 255
	EndIf
	ProcedureReturn a
EndProcedure
Procedure MirrorImage(ID)

	Structure RGB
		Blue.c
		Green.c
		Red.c
	EndStructure

	Protected x,y,n
	Protected *Point.RGB
	Protected *Image,*Swap
	Protected ImageInfo.BITMAP
	Protected ImageWidth,ImageHeight,ImageBytes,ImageLine
	Protected Memory

	If GetObject_(ID,SizeOf(BITMAP),@ImageInfo)
		ImageWidth=ImageInfo\bmWidth
		ImageHeight=ImageInfo\bmHeight
		ImageBytes=ImageInfo\bmBitsPixel>>3
		ImageLine=ImageWidth*ImageBytes

		; Abdunkeln/oder Aufhellen...
		*Image=ImageInfo\bmBits
		For y=0 To ImageHeight-1
			n=(y<<8)/ImageHeight;	Skalieren 0...255
			; Debug Str(y)+" - "+Str(n)
			For x=0 To ImageWidth-1
				*Point=*Image + (x*ImageBytes) + (y*ImageLine)
				*Point\Red=ScaleByte(*Point\Red,n)
				*Point\Green=ScaleByte(*Point\Green,n)
				*Point\Blue=ScaleByte(*Point\Blue,n)
			Next
		Next

		; Spiegeln...
		*Image=ImageInfo\bmBits
		Memory=AllocateMemory(ImageLine)
		*Swap=*Image + ((ImageHeight-1)*ImageLine)
		For y=0 To (ImageHeight>>1)-1
			CopyMemory(*Image,Memory,ImageLine)
			CopyMemory(*Swap,*Image,ImageLine)
			CopyMemory(Memory,*Swap,ImageLine)
			*Image+ImageLine
			*Swap-ImageLine
		Next
		If Memory : FreeMemory(Memory) : EndIf

	EndIf


EndProcedure

GlossyID=LoadImage(#ImageCase,"Data\CoverCase.png")
ImageID=LoadImage(#ImageCover,"Data\Cover.bmp")
; Nur bei Neuberechnung des Bildes bekommt *Image in MirrorImage einen Wert zugewiesen...

If #CompressMirror; Spiegelbild verkürzen...
	y=(CoverSize+MirrorHeight*3)>>2
	GrabImage(#ImageCover,#ImageMirror,0,CoverSize-y,CoverSize,y)
	ResizeImage(#ImageMirror,CoverSize,MirrorHeight,#PB_Image_Smooth)
Else
	y=MirrorHeight
	GrabImage(#ImageCover,#ImageMirror,0,CoverSize-y,CoverSize,y)
EndIf

ShadowID=ImageID(#ImageMirror)
MirrorImage(ShadowID)

; GEHT NICH?!
StartDrawing(ImageOutput(#ImageCover))
DrawAlphaImage(GlossyID,0,0)
StopDrawing()

WinID=OpenWindow(0,0,0,340,540,"CD-Cover",#WS_OVERLAPPEDWINDOW)
SetWindowColor(0,ColorBackground)

Repeat
	StartDrawing(WindowOutput(0))
	DrawImage(ImageID,20,20)
	DrawImage(ShadowID,20,20+CoverSize)
	StopDrawing()
Until WaitWindowEvent(100)=#WM_CHAR
c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

Post by c4s »

I'm interested in nice effects. So please post those two pics somewhere Michael!
User avatar
Michael Vogel
Addict
Addict
Posts: 2797
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Post by Michael Vogel »

c4s wrote:I'm interested in nice effects. So please post those two pics somewhere Michael!
http://sudokuprogram.googlepages.com/Covers.zip
c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

Post by c4s »

Michael Vogel wrote:
c4s wrote:I'm interested in nice effects. So please post those two pics somewhere Michael!
http://sudokuprogram.googlepages.com/Covers.zip
Well, there is potential ;D

I'm working on something like that too - but...still working...........
Post Reply