Page 1 of 1

Module TagReader Crossplattform

Posted: Sun Aug 11, 2013 9:42 pm
by ts-soft
This Module read MP3v1 and MP3v2 Tags from MP3-Files.
Only Texttags supported.

For using, see the example at the end of code!

Code: Select all

;======================================================================
; Module:          TagReader.pbi
;
; Author:          Thomas (ts-soft) Schulz
; Date:            Oct 18, 2013
; Version:         0.4
; Target Compiler: PureBasic 5.2+
; Target OS:       All
; License:         Free, unrestricted, no warranty whatsoever
;                  Use at your own risk
;======================================================================

; History:
; Version 0.4
; fixed: Reading Unicode Tags (ID3v2.x) by Thorsten1867

; Version 0.3
; + added genre and track v1 Tags
; fixed a bug with only v1 Tags
;
; Version 0.2
; fixed a bug in Reading v1 Tags

DeclareModule TagReader
  Interface iTagReader
    Destroy()
    GetTagList.s()
    GetContent.s(Tag.s)
  EndInterface
 
  Declare New_MP3TagObj(FileName.s)
EndDeclareModule

Module TagReader
  EnableExplicit
 
  Structure TagReader
    ID.a[3]
    version.a[2]
    flags.b
    size.l
    *memory
  EndStructure
 
  Structure TagReader_Class
    *vTable
    Map Tags.s()
    AvailableTags.s
  EndStructure
 
  Procedure Destroy(*obj.TagReader_Class)
    If *obj
      FreeMap(*obj\Tags())
      FreeMemory(*obj)
    EndIf
  EndProcedure
 
  Procedure.s GetTagList(*obj.TagReader_Class)
    If *obj
      ProcedureReturn *obj\AvailableTags
    EndIf
  EndProcedure
 
  Procedure.s GetContent(*obj.TagReader_Class, Tag.s)
    If *obj
      ProcedureReturn *obj\Tags(Tag)
    EndIf
  EndProcedure
 
 Procedure New_MP3TagObj(FileName.s)
  ; fixed: Reading Unicode Tags (ID3v2.x) by Thorsten1867
  Protected *obj.TagReader_Class
  Protected FF, i, pos, size, TextEncoding.b, ID3.TagReader
  Protected frame, FrameID.s, content.s, *mem, b
  Dim genre.s(147)
 
  If FileSize(FileName) <= 0 : ProcedureReturn #False : EndIf
 
  *obj = AllocateMemory(SizeOf(TagReader_Class))
 
  If *obj
    InitializeStructure(*obj, TagReader_Class)
    *obj\vTable = ?vTable
   
    Restore genre
    For i = 0 To 146
      Read.s genre(i)
    Next
   
    FF = ReadFile(#PB_Any, FileName)
    If FF
     
      ;{ ----- ID3v1 -----
      FileSeek(FF, Lof(FF) - 128)
      *mem = AllocateMemory(128)
      If *mem
        ReadData(FF, *mem, 128)
        If PeekS(*mem, 3, #PB_Ascii) = "TAG"
          content = Trim(PeekS(*mem + 3, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("TITLE") = content
            *obj\AvailableTags + "TITLE" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 33, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("ARTIST") = content
            *obj\AvailableTags + "ARTIST" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 63, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("ALBUM") = content
            *obj\AvailableTags + "ALBUM" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 93, 4, #PB_Ascii))
          If content <> ""
            *obj\Tags("YEAR") = content
            *obj\AvailableTags + "YEAR" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 97, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("COMMENT") = content
            *obj\AvailableTags + "COMMENT" + #LF$
          EndIf
          b = PeekA(*mem + 126)
          If b > 0
            *obj\Tags("TRACK") = Str(b)
            *obj\AvailableTags + "TRACK" + #LF$
          EndIf
          b = PeekA(*mem + 127)
          If b > -1 And b < 147
            *obj\Tags("GENRE") = genre(b)
            *obj\AvailableTags + "GENRE" + #LF$
          EndIf
        EndIf
        FreeMemory(*mem)
      EndIf ;}
     
      ;{ ----- ID3v2.x -----
      FileSeek(FF, 0)
      ReadData(FF, @ID3, 6)
      If PeekS(@ID3\ID[0], 3, #PB_Ascii) <> "ID3" ;{ kein ID3v2 vorhanden
        CloseFile(FF)
        If *obj\AvailableTags = ""
          FreeMemory(*obj)
          ProcedureReturn #False
        Else
          ProcedureReturn *obj
        EndIf
      EndIf ;}
      For i = 3 To 0 Step -1
        ID3\size + ReadAsciiCharacter(FF) << (7 * i)
      Next
      If ID3\size > 0
        ID3\memory = AllocateMemory(ID3\size)
        ReadData(FF, ID3\memory, ID3\size)
      EndIf
      CloseFile(FF)
     
      If ID3\memory > 0
        ; --- FrameHeader (10 Byte) ---
        Repeat
          size = 0
          frame = PeekL(ID3\memory + pos)
          If frame
            ; FrameID (4 Byte)
            FrameID = PeekS(@frame, 4, #PB_Ascii)
            pos + SizeOf(Long)
            If FrameID <> ""
              ; FrameSize (4 Byte)
              For i = 3 To 0 Step -1
                size + PeekA(ID3\memory + pos) << (7 * i)
                pos + SizeOf(Ascii)
              Next
              ; Flags (2 Byte)
              pos + 2
              ; TextEncoding (1 Byte)
              TextEncoding = PeekB(ID3\memory + pos)&$FF
              pos + 1
              ; Content (size-1)
              If pos + size <= ID3\size
                If TextEncoding = 0
                  content = PeekS(ID3\memory + pos, size-1, #PB_Ascii)
                ElseIf TextEncoding = 1
                  content = PeekS(ID3\memory + pos + 2, (size / 2) -1, #PB_Unicode)
                EndIf
              Else
                Break
              EndIf
              pos + size - 1
              If content <> ""
                If Left(content, 1) <> Chr(255)
                  *obj\Tags(FrameID) = content
                  *obj\AvailableTags + FrameID + #LF$
                EndIf
              EndIf
            EndIf
          Else
            Break
          EndIf
        Until pos >= ID3\size
        *obj\AvailableTags = Left(*obj\AvailableTags, Len(*obj\AvailableTags) - 1)
        FreeMemory(ID3\memory)
        ProcedureReturn *obj
      EndIf ;}
     
    EndIf
   
  EndIf
 
  ProcedureReturn #False
EndProcedure 
 
  DataSection
    vTable:
    Data.i @Destroy()
    Data.i @GetTagList()
    Data.i @GetContent()
    genre:
    Data.s "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal"
    Data.s "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial"
    Data.s "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk"
    Data.s "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise"
    Data.s "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic"
    Data.s "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta"
    Data.s "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes"
    Data.s "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock"
    Data.s "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass"
    Data.s "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic"
    Data.s "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove"
    Data.s "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle"
    Data.s "Duet", "Punk Rock", "Drum Solo", "Acapella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "indie", "Brit Pop", "Negerpunk"
    Data.s "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", "Comteporary Christian"
    Data.s "Christian Rock", "Merengue", "Salsa", "Trash Metal", "Anime", "JPop", "Synth Pop"
  EndDataSection
EndModule

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
 
  Define.s MP3, tList
  Define.i i, c
  Define.TagReader::iTagReader TR
 
  MP3 = OpenFileRequester("", "Select MP3 File", "MP3 Files (*.mp3)|*.mp3", 0)
  If MP3 <> ""
    TR = TagReader::New_MP3TagObj(MP3)
    If TR
      tList = TR\GetTagList()
      c = CountString(tList, #LF$)
      For i = 1 To c
        Debug StringField(tList, i, #LF$) + "  " + TR\GetContent(StringField(tList, i, #LF$))
      Next
      TR\Destroy()
    EndIf
  EndIf
CompilerEndIf 
Have fun

Re: Module TagReader Crossplattform

Posted: Sun Aug 11, 2013 9:52 pm
by rsts
Another nice one, which I'll probably use in a quick utility.

Thanks once again for your contribution(s).

cheers

Re: Module TagReader Crossplattform

Posted: Sun Aug 11, 2013 10:10 pm
by davido
Very nice code - lots to learn!

Thanks for sharing. :D

Re: Module TagReader Crossplattform

Posted: Sun Aug 11, 2013 11:41 pm
by ts-soft
Update:
History wrote:; Version 0.2
; fixed a bug in Reading v1 Tags
History wrote:; Version 0.3
; + added genre and track v1 Tags
; fixed a bug with only v1 Tags

Re: Module TagReader Crossplattform

Posted: Mon Aug 12, 2013 8:43 am
by Kwai chang caine
Thanks 8)

Re: Module TagReader Crossplattform

Posted: Sun Oct 13, 2013 8:58 pm
by Thorsten1867
Long Tags will be cut.
e.g. album title "Morgenstimmung auf einem persischem Markt" => "Morgenstimmung auf einem persi"
I can't find the reason in the source code.

Re: Module TagReader Crossplattform

Posted: Sun Oct 13, 2013 9:30 pm
by davido
Isn't it due to the length specified in PeekS in Procedure New_MP3TagObj?

Re: Module TagReader Crossplattform

Posted: Sun Oct 13, 2013 9:35 pm
by ts-soft
v1 Tags a limited to 29 chars!
Only v2.xx Tags supports longer Text.

Re: Module TagReader Crossplattform

Posted: Mon Oct 14, 2013 2:49 pm
by Thorsten1867
ts-soft wrote:v1 Tags a limited to 29 chars!
Only v2.xx Tags supports longer Text.
But MP3Tags says "ID3v2.3 (ID3v1 ID3v2.3)" and the result of TagReader is:

Code: Select all

TITLE  Nußknacker-Suite - Miniatur-Ou
ARTIST  Rolf Zuckowski
ALBUM  Morgenstimmung auf einem persi
TRACK  4
TRCK  04

Re: Module TagReader Crossplattform

Posted: Wed Oct 16, 2013 7:14 pm
by Thorsten1867
It seems to be a problem with the v2 tag code. (Only the v1 tags will be shown)

Code: Select all

If frame
  FrameID = PeekS(@frame, 4, #PB_Ascii)
  Debug "FrameID: "+FrameID
  pos + SizeOf(Long)
  If FrameID <> ""
    For i = 3 To 0 Step -1
       size + PeekA(ID3\memory + pos) << (7 * i) : pos + SizeOf(Ascii)
    Next
    size - 1 : pos + 3
    Debug "Size: "+STR(size)
    If pos + size <= ID3\size
      content = PeekS(ID3\memory + pos, size, #PB_Ascii)
      Debug "Content: "+content
    Else
      Break
    EndIf
    pos + size
    If content <> ""
      If Left(content, 1) <> Chr(255)
        *obj\Tags(FrameID) = content
        *obj\AvailableTags + FrameID + #LF$
      EndIf
    EndIf
  EndIf
Else
  Break
EndIf
I get a wrong 'size' (84 instead of 41) and 'content' shows two invalid charakters and after it only the first character (= pos+2) of the tag.

Re: Module TagReader Crossplattform

Posted: Fri Oct 18, 2013 7:22 pm
by Thorsten1867
I think, I found the problem (Unicode Tags).

Here my BugFix:

Code: Select all

Procedure New_MP3TagObj(FileName.s)
  ; fixed: Reading Unicode Tags (ID3v2.x) by Thorsten1867
  Protected *obj.TagReader_Class
  Protected FF, i, pos, size, TextEncoding.b, ID3.TagReader
  Protected frame, FrameID.s, content.s, *mem, b
  Dim genre.s(147)
  
  If FileSize(FileName) <= 0 : ProcedureReturn #False : EndIf
  
  *obj = AllocateMemory(SizeOf(TagReader_Class))
  
  If *obj
    InitializeStructure(*obj, TagReader_Class)
    *obj\vTable = ?vTable
    
    Restore genre
    For i = 0 To 146
      Read.s genre(i)
    Next
    
    FF = ReadFile(#PB_Any, FileName)
    If FF
      
      ;{ ----- ID3v1 -----
      FileSeek(FF, Lof(FF) - 128)
      *mem = AllocateMemory(128)
      If *mem
        ReadData(FF, *mem, 128)
        If PeekS(*mem, 3, #PB_Ascii) = "TAG"
          content = Trim(PeekS(*mem + 3, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("TITLE") = content
            *obj\AvailableTags + "TITLE" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 33, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("ARTIST") = content
            *obj\AvailableTags + "ARTIST" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 63, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("ALBUM") = content
            *obj\AvailableTags + "ALBUM" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 93, 4, #PB_Ascii))
          If content <> ""
            *obj\Tags("YEAR") = content
            *obj\AvailableTags + "YEAR" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 97, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("COMMENT") = content
            *obj\AvailableTags + "COMMENT" + #LF$
          EndIf
          b = PeekA(*mem + 126)
          If b > 0
            *obj\Tags("TRACK") = Str(b)
            *obj\AvailableTags + "TRACK" + #LF$
          EndIf
          b = PeekA(*mem + 127)
          If b > -1 And b < 147
            *obj\Tags("GENRE") = genre(b)
            *obj\AvailableTags + "GENRE" + #LF$
          EndIf
        EndIf
        FreeMemory(*mem)
      EndIf ;}
      
      ;{ ----- ID3v2.x -----
      FileSeek(FF, 0)
      ReadData(FF, @ID3, 6)
      If PeekS(@ID3\ID[0], 3, #PB_Ascii) <> "ID3" ;{ kein ID3v2 vorhanden
        CloseFile(FF)
        If *obj\AvailableTags = ""
          FreeMemory(*obj)
          ProcedureReturn #False
        Else
          ProcedureReturn *obj
        EndIf
      EndIf ;}
      For i = 3 To 0 Step -1
        ID3\size + ReadAsciiCharacter(FF) << (7 * i)
      Next
      If ID3\size > 0
        ID3\memory = AllocateMemory(ID3\size)
        ReadData(FF, ID3\memory, ID3\size)
      EndIf
      CloseFile(FF)
      
      If ID3\memory > 0
        ; --- FrameHeader (10 Byte) ---
        Repeat
          size = 0
          frame = PeekL(ID3\memory + pos)
          If frame
            ; FrameID (4 Byte)
            FrameID = PeekS(@frame, 4, #PB_Ascii)
            pos + SizeOf(Long)
            If FrameID <> ""
              ; FrameSize (4 Byte)
              For i = 3 To 0 Step -1
                size + PeekA(ID3\memory + pos) << (7 * i)
                pos + SizeOf(Ascii)
              Next
              ; Flags (2 Byte)
              pos + 2
              ; TextEncoding (1 Byte)
              TextEncoding = PeekB(ID3\memory + pos)&$FF
              pos + 1
              ; Content (size-1)
              If pos + size <= ID3\size
                If TextEncoding = 0
                  content = PeekS(ID3\memory + pos, size-1, #PB_Ascii)
                ElseIf TextEncoding = 1
                  content = PeekS(ID3\memory + pos + 2, (size/2)-1, #PB_Unicode)
                EndIf
              Else
                Break
              EndIf 
              pos + size - 1
              If content <> ""
                If Left(content, 1) <> Chr(255)
                  *obj\Tags(FrameID) = content
                  *obj\AvailableTags + FrameID + #LF$
                EndIf
              EndIf
            EndIf
          Else
            Break
          EndIf
        Until pos >= ID3\size
        *obj\AvailableTags = Left(*obj\AvailableTags, Len(*obj\AvailableTags) - 1)
        FreeMemory(ID3\memory)
        ProcedureReturn *obj
      EndIf ;}
      
    EndIf
    
  EndIf
  
  ProcedureReturn #False
EndProcedure

Re: Module TagReader Crossplattform

Posted: Fri Oct 18, 2013 7:35 pm
by ts-soft
Thx :D
Updated Source in first post.

Re: Module TagReader Crossplattform

Posted: Sun Jun 26, 2016 7:03 pm
by mrv2k
Is there any way this can be updated to take into account the embedded picture APIC tag as it prevents the ID3 v2.3 part from working. I've played around for hours but I cant get the code to bypass the tag and data.

Re: Module TagReader Crossplattform

Posted: Thu Jul 28, 2016 12:26 pm
by mrv2k
OK...

I've hacked in a workaround for MP3's with embedded art. Now all ID3 v2.x and v3.X frames work AFAIK.

Code: Select all

;======================================================================
; Module:          TagReader.pbi
;
; Author:          Thomas (ts-soft) Schulz
; Date:            Oct 18, 2013
; Version:         0.4
; Target Compiler: PureBasic 5.2+
; Target OS:       All
; License:         Free, unrestricted, no warranty whatsoever
;                  Use at your own risk
;======================================================================

; History:
; Version 0.4
; fixed: Reading Unicode Tags (ID3v2.x) by Thorsten1867

; Version 0.3
; + added genre and track v1 Tags
; fixed a bug with only v1 Tags
;
; Version 0.2
; fixed a bug in Reading v1 Tags

DeclareModule TagReader
  Interface iTagReader
    Destroy()
    GetTagList.s()
    GetContent.s(Tag.s)
  EndInterface
 
  Declare New_MP3TagObj(FileName.s)
EndDeclareModule

Module TagReader
  EnableExplicit
 
  Structure TagReader
    ID.a[3]
    version.a[2]
    flags.b
    size.l
    *memory
  EndStructure
 
  Structure TagReader_Class
    *vTable
    Map Tags.s()
    AvailableTags.s
  EndStructure
 
  Procedure Destroy(*obj.TagReader_Class)
    If *obj
      FreeMap(*obj\Tags())
      FreeMemory(*obj)
    EndIf
  EndProcedure
 
  Procedure.s GetTagList(*obj.TagReader_Class)
    If *obj
      ProcedureReturn *obj\AvailableTags
    EndIf
  EndProcedure
 
  Procedure.s GetContent(*obj.TagReader_Class, Tag.s)
    If *obj
      ProcedureReturn *obj\Tags(Tag)
    EndIf
  EndProcedure
 
 Procedure New_MP3TagObj(FileName.s)
  ; fixed: Reading Unicode Tags (ID3v2.x) by Thorsten1867
  Protected *obj.TagReader_Class
  Protected FF, i, pos, size, TextEncoding.b, ID3.TagReader
  Protected frame, FrameID.s, content.s, *mem, b
  Dim genre.s(147)
 
  If FileSize(FileName) <= 0 : ProcedureReturn #False : EndIf
 
  *obj = AllocateMemory(SizeOf(TagReader_Class))
 
  If *obj
    InitializeStructure(*obj, TagReader_Class)
    *obj\vTable = ?vTable
   
    Restore genre
    For i = 0 To 146
      Read.s genre(i)
    Next
   
    FF = ReadFile(#PB_Any, FileName)
    If FF
     
      ;{ ----- ID3v1 -----
      FileSeek(FF, Lof(FF) - 128)
      *mem = AllocateMemory(128)
      If *mem
        ReadData(FF, *mem, 128)
        If PeekS(*mem, 3, #PB_Ascii) = "TAG"
          content = Trim(PeekS(*mem + 3, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("TITLE") = content
            *obj\AvailableTags + "TITLE" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 33, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("ARTIST") = content
            *obj\AvailableTags + "ARTIST" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 63, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("ALBUM") = content
            *obj\AvailableTags + "ALBUM" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 93, 4, #PB_Ascii))
          If content <> ""
            *obj\Tags("YEAR") = content
            *obj\AvailableTags + "YEAR" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 97, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("COMMENT") = content
            *obj\AvailableTags + "COMMENT" + #LF$
          EndIf
          b = PeekA(*mem + 126)
          If b > 0
            *obj\Tags("TRACK") = Str(b)
            *obj\AvailableTags + "TRACK" + #LF$
          EndIf
          b = PeekA(*mem + 127)
          If b > -1 And b < 147
            *obj\Tags("GENRE") = genre(b)
            *obj\AvailableTags + "GENRE" + #LF$
          EndIf
        EndIf
        FreeMemory(*mem)
      EndIf ;}
     
      ;{ ----- ID3v2.x -----
      FileSeek(FF, 0)
      ReadData(FF, @ID3, 6)
      If PeekS(@ID3\ID[0], 3, #PB_Ascii) <> "ID3" ;{ kein ID3v2 vorhanden
        CloseFile(FF)
        If *obj\AvailableTags = ""
          FreeMemory(*obj)
          ProcedureReturn #False
        Else
          ProcedureReturn *obj
        EndIf
      EndIf ;}
      For i = 3 To 0 Step -1
        ID3\size + ReadAsciiCharacter(FF) << (7 * i)
      Next
      If ID3\size > 0
        ID3\memory = AllocateMemory(ID3\size)
        ReadData(FF, ID3\memory, ID3\size)
      EndIf
      CloseFile(FF)
     
If ID3\memory > 0
        ; --- FrameHeader (10 Byte) ---
        Repeat
          size = 0
          frame = PeekL(ID3\memory + pos)
          If frame
            ; FrameID (4 Byte)
            FrameID = PeekS(@frame, 4, #PB_Ascii)
            pos + SizeOf(Long)    
            ;
            ; Remove APIC Frame and Data
            ;
            If FrameID = "APIC"
              ; FrameSize (4 Byte)
              For i = 3 To 0 Step -1
                size + PeekA(ID3\memory + pos) << (7 * i)
                pos + SizeOf(Ascii)
              Next
              pos + size - 1
              pos+pos-39 ; Hack to move position
            EndIf  
            ;
            ;
            ;
            If FrameID <> "" And FrameID <> "APIC"
              ; FrameSize (4 Byte)
              For i = 3 To 0 Step -1
                size + PeekA(ID3\memory + pos) << (7 * i)
                pos + SizeOf(Ascii)
              Next
              ; Flags (2 Byte)
              pos + 2
              ; TextEncoding (1 Byte)
              TextEncoding = PeekB(ID3\memory + pos)&$FF
              pos + 1
              ; Content (size-1)
              If pos + size <= ID3\size
                If TextEncoding = 0
                  content = PeekS(ID3\memory + pos, size-1, #PB_Ascii)
                ElseIf TextEncoding = 1
                  content = PeekS(ID3\memory + pos + 2, (size / 2) -1, #PB_Unicode)
                EndIf
              Else
                Break
              EndIf
              pos + size - 1
              If content <> ""
                If Left(content, 1) <> Chr(255)
                  *obj\Tags(FrameID) = content
                  *obj\AvailableTags + FrameID + #LF$
                EndIf
              EndIf
            EndIf
          Else
            Break
          EndIf
        Until pos >= ID3\size
        *obj\AvailableTags = Left(*obj\AvailableTags, Len(*obj\AvailableTags) - 1)
        FreeMemory(ID3\memory)
        ProcedureReturn *obj
      EndIf ;}
     
    EndIf
   
  EndIf
 
  ProcedureReturn #False
EndProcedure 
 
  DataSection
    vTable:
    Data.i @Destroy()
    Data.i @GetTagList()
    Data.i @GetContent()
    genre:
    Data.s "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal"
    Data.s "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial"
    Data.s "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk"
    Data.s "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise"
    Data.s "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic"
    Data.s "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta"
    Data.s "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes"
    Data.s "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock"
    Data.s "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass"
    Data.s "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic"
    Data.s "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove"
    Data.s "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle"
    Data.s "Duet", "Punk Rock", "Drum Solo", "Acapella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "indie", "Brit Pop", "Negerpunk"
    Data.s "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", "Comteporary Christian"
    Data.s "Christian Rock", "Merengue", "Salsa", "Trash Metal", "Anime", "JPop", "Synth Pop"
  EndDataSection
EndModule

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
 
  Define.s MP3, tList
  Define.i i, c
  Define.TagReader::iTagReader TR
 
  MP3 = OpenFileRequester("", "Select MP3 File", "MP3 Files (*.mp3)|*.mp3", 0)
  If MP3 <> ""
    TR = TagReader::New_MP3TagObj(MP3)
    If TR
      tList = TR\GetTagList()
      c = CountString(tList, #LF$)
      For i = 1 To c
        Debug StringField(tList, i, #LF$) + "  " + TR\GetContent(StringField(tList, i, #LF$))
      Next
      TR\Destroy()
    EndIf
  EndIf
CompilerEndIf
It's scrappy, but it works!

Regards

Paul

Re: Module TagReader Crossplattform

Posted: Fri Jul 29, 2016 2:55 pm
by mrv2k
Removed the hack. This code should calculate the frame size better.

Code: Select all

;======================================================================
; Module:          TagReader.pbi
;
; Author:          Thomas (ts-soft) Schulz
; Date:            Oct 18, 2013
; Version:         0.4
; Target Compiler: PureBasic 5.2+
; Target OS:       All
; License:         Free, unrestricted, no warranty whatsoever
;                  Use at your own risk
;======================================================================

; History:
; Version 0.4
; fixed: Reading Unicode Tags (ID3v2.x) by Thorsten1867

; Version 0.3
; + added genre and track v1 Tags
; fixed a bug with only v1 Tags
;
; Version 0.2
; fixed a bug in Reading v1 Tags

DeclareModule TagReader
  Interface iTagReader
    Destroy()
    GetTagList.s()
    GetContent.s(Tag.s)
  EndInterface
 
  Declare New_MP3TagObj(FileName.s)
EndDeclareModule

Module TagReader
  EnableExplicit
 
  Structure TagReader
    ID.a[3]
    version.a[2]
    flags.b
    size.l
    *memory
  EndStructure
 
  Structure TagReader_Class
    *vTable
    Map Tags.s()
    AvailableTags.s
  EndStructure
 
  Procedure Destroy(*obj.TagReader_Class)
    If *obj
      FreeMap(*obj\Tags())
      FreeMemory(*obj)
    EndIf
  EndProcedure
 
  Procedure.s GetTagList(*obj.TagReader_Class)
    If *obj
      ProcedureReturn *obj\AvailableTags
    EndIf
  EndProcedure
 
  Procedure.s GetContent(*obj.TagReader_Class, Tag.s)
    If *obj
      ProcedureReturn *obj\Tags(Tag)
    EndIf
  EndProcedure
 
 Procedure New_MP3TagObj(FileName.s)
  ; fixed: Reading Unicode Tags (ID3v2.x) by Thorsten1867
  Protected *obj.TagReader_Class
  Protected FF, i, pos, size, TextEncoding.b, ID3.TagReader
  Protected frame, FrameID.s, content.s, *mem, b
  Dim genre.s(147)

  If FileSize(FileName) <= 0 : ProcedureReturn #False : EndIf
 
  *obj = AllocateMemory(SizeOf(TagReader_Class))
 
  If *obj
    InitializeStructure(*obj, TagReader_Class)
    *obj\vTable = ?vTable
   
    Restore genre
    For i = 0 To 146
      Read.s genre(i)
    Next
   
    FF = ReadFile(#PB_Any, FileName,#PB_File_SharedRead)
    If FF
      ;{ ----- ID3v1 -----
      FileSeek(FF, Lof(FF) - 128)
      *mem = AllocateMemory(128)
      If *mem
        ReadData(FF, *mem, 128)
        If PeekS(*mem, 3, #PB_Ascii) = "TAG"
          content = Trim(PeekS(*mem + 3, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("TITLE") = content
            *obj\AvailableTags + "TITLE" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 33, 30, #PB_Ascii))          
          If content <> ""
            *obj\Tags("ARTIST") = content
            *obj\AvailableTags + "ARTIST" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 63, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("ALBUM") = content
            *obj\AvailableTags + "ALBUM" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 93, 4, #PB_Ascii))
          If content <> ""
            *obj\Tags("YEAR") = content
            *obj\AvailableTags + "YEAR" + #LF$
          EndIf
          content = Trim(PeekS(*mem + 97, 30, #PB_Ascii))
          If content <> ""
            *obj\Tags("COMMENT") = content
            *obj\AvailableTags + "COMMENT" + #LF$
          EndIf
          b = PeekA(*mem + 126)
          If b > 0
            *obj\Tags("TRACK") = Str(b)
            *obj\AvailableTags + "TRACK" + #LF$
          EndIf
          b = PeekA(*mem + 127)
          If b > -1 And b < 147
            *obj\Tags("GENRE") = genre(b)
            *obj\AvailableTags + "GENRE" + #LF$
          EndIf
        EndIf
        FreeMemory(*mem)
      EndIf ;}
     
      ;{ ----- ID3v2.x -----
      FileSeek(FF, 0)
      ReadData(FF, @ID3, 6)
      If PeekS(@ID3\ID[0], 3, #PB_Ascii) <> "ID3" ;{ kein ID3v2 vorhanden
        CloseFile(FF)
        If *obj\AvailableTags = ""
          FreeMemory(*obj)
          ProcedureReturn #False
        Else
          ProcedureReturn *obj
        EndIf
      EndIf ;}
      For i = 3 To 0 Step -1
        ID3\size + ReadAsciiCharacter(FF) << (7 * i)
      Next
      If ID3\size > 0
        ID3\memory = AllocateMemory(ID3\size)
        ReadData(FF, ID3\memory, ID3\size)
      EndIf
      CloseFile(FF)
     
      If ID3\memory > 0
        ; --- FrameHeader (10 Byte) ---
        Repeat
          size = 0
          frame = PeekL(ID3\memory + pos)
          If frame
            ; FrameID (4 Byte)
            FrameID = PeekS(@frame, 4, #PB_Ascii)
            pos + SizeOf(Long)     
            If FrameID <> ""
              ; FrameSize (4 Byte)              
              For i = 0 To 3
                size= (size << 8) | PeekA(ID3\memory + pos + i)
              Next i   
              ; Move past frame header 4 bytes              
              pos + 4              
              ; Flags (2 Byte)
              pos + 2
              ; TextEncoding (1 Byte)
              TextEncoding = PeekB(ID3\memory + pos)&$FF
              pos + 1
              If pos + size <= ID3\size
                If TextEncoding = 0
                  content = PeekS(ID3\memory + pos, size-1, #PB_Ascii)
                ElseIf TextEncoding = 1
                  content = PeekS(ID3\memory + pos + 2, (size / 2) -1, #PB_Unicode)
                EndIf
              Else
                Break
              EndIf
              pos + size - 1
              If content <> ""
                If Left(content, 1) <> Chr(255)
                  *obj\Tags(FrameID) = content
                  *obj\AvailableTags + FrameID + #LF$
                EndIf
              EndIf
            EndIf
          Else
            Break
          EndIf
        Until pos >= ID3\size
        *obj\AvailableTags = Left(*obj\AvailableTags, Len(*obj\AvailableTags) - 1)
        FreeMemory(ID3\memory)
        ProcedureReturn *obj
      EndIf ;}
     
    EndIf
   
  EndIf
 
  ProcedureReturn #False
EndProcedure 
 
  DataSection
    vTable:
    Data.i @Destroy()
    Data.i @GetTagList()
    Data.i @GetContent()
    genre:
    Data.s "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal"
    Data.s "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial"
    Data.s "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk"
    Data.s "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise"
    Data.s "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic"
    Data.s "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta"
    Data.s "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes"
    Data.s "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock"
    Data.s "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass"
    Data.s "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic"
    Data.s "Humour", "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove"
    Data.s "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle"
    Data.s "Duet", "Punk Rock", "Drum Solo", "Acapella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "indie", "Brit Pop", "Negerpunk"
    Data.s "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", "Comteporary Christian"
    Data.s "Christian Rock", "Merengue", "Salsa", "Trash Metal", "Anime", "JPop", "Synth Pop"
  EndDataSection
EndModule

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
 
  Define.s MP3, tList
  Define.i i, c
  Define.TagReader::iTagReader TR
 
  MP3 = OpenFileRequester("", "Select MP3 File", "MP3 Files (*.mp3)|*.mp3", 0)
  If MP3 <> ""
    TR = TagReader::New_MP3TagObj(MP3)
    If TR
      tList = TR\GetTagList()
      c = CountString(tList, #LF$)
      For i = 1 To c
        Debug StringField(tList, i, #LF$) + "  " + TR\GetContent(StringField(tList, i, #LF$))
      Next
      TR\Destroy()
    EndIf
  EndIf
CompilerEndIf