Module TagReader Crossplattform

Share your advanced PureBasic knowledge/code with the community.
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Module TagReader Crossplattform

Post 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
Last edited by ts-soft on Mon Aug 12, 2013 12:20 am, edited 2 times in total.
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Re: Module TagReader Crossplattform

Post by rsts »

Another nice one, which I'll probably use in a quick utility.

Thanks once again for your contribution(s).

cheers
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Module TagReader Crossplattform

Post by davido »

Very nice code - lots to learn!

Thanks for sharing. :D
DE AA EB
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: Module TagReader Crossplattform

Post 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
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: Module TagReader Crossplattform

Post by Kwai chang caine »

Thanks 8)
ImageThe happiness is a road...
Not a destination
User avatar
Thorsten1867
Addict
Addict
Posts: 1372
Joined: Wed Aug 24, 2005 4:02 pm
Location: Germany

Re: Module TagReader Crossplattform

Post 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.
Translated with http://www.DeepL.com/Translator

Download of PureBasic - Modules
Download of PureBasic - Programs

[Windows 11 x64] [PB V5.7x]
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: Module TagReader Crossplattform

Post by davido »

Isn't it due to the length specified in PeekS in Procedure New_MP3TagObj?
DE AA EB
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: Module TagReader Crossplattform

Post by ts-soft »

v1 Tags a limited to 29 chars!
Only v2.xx Tags supports longer Text.
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
User avatar
Thorsten1867
Addict
Addict
Posts: 1372
Joined: Wed Aug 24, 2005 4:02 pm
Location: Germany

Re: Module TagReader Crossplattform

Post 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
Translated with http://www.DeepL.com/Translator

Download of PureBasic - Modules
Download of PureBasic - Programs

[Windows 11 x64] [PB V5.7x]
User avatar
Thorsten1867
Addict
Addict
Posts: 1372
Joined: Wed Aug 24, 2005 4:02 pm
Location: Germany

Re: Module TagReader Crossplattform

Post 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.
Translated with http://www.DeepL.com/Translator

Download of PureBasic - Modules
Download of PureBasic - Programs

[Windows 11 x64] [PB V5.7x]
User avatar
Thorsten1867
Addict
Addict
Posts: 1372
Joined: Wed Aug 24, 2005 4:02 pm
Location: Germany

Re: Module TagReader Crossplattform

Post 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
Translated with http://www.DeepL.com/Translator

Download of PureBasic - Modules
Download of PureBasic - Programs

[Windows 11 x64] [PB V5.7x]
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: Module TagReader Crossplattform

Post by ts-soft »

Thx :D
Updated Source in first post.
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
User avatar
mrv2k
User
User
Posts: 53
Joined: Fri Jan 20, 2012 3:40 pm
Location: SE England
Contact:

Re: Module TagReader Crossplattform

Post 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.
User avatar
mrv2k
User
User
Posts: 53
Joined: Fri Jan 20, 2012 3:40 pm
Location: SE England
Contact:

Re: Module TagReader Crossplattform

Post 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
User avatar
mrv2k
User
User
Posts: 53
Joined: Fri Jan 20, 2012 3:40 pm
Location: SE England
Contact:

Re: Module TagReader Crossplattform

Post 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
Post Reply