Visual Basic 6 function "get_audio_tag"

Go back

Below you'll find the source for the Visual Basic 6 function get_audio_tag.

Attribute VB_Name = "modGetAudioTag"
' These functions are downloaded from:
' http://www.stefanthoolen.nl/archive/vb6-functions/
' 
' You may freely distribute this file but please leave all comments, including this one, in it.
' 
' @Author Stefan Thoolen <mail@stefanthoolen.nl>

Option Explicit

Public Type audio_tag
    type As String
    has_track As Boolean
    track As Integer
    artist As String
    title As String
    album As String
    year As String
    genre As String
    comment As String
End Type

' Public type, will contain all fetched information
Public Type asf_tag
    FP_Playtime As Long
    FP_Filesize As Long
    FP_Bitrate As Long
    CD_Found As Boolean
    CD_Title As String
    CD_Author As String
    CD_Copyright As String
    CD_Description As String
    CD_Rating As String
    ECD_Found As Boolean
    ECD_Genre As String
    ECD_AlbumTitle As String
    ECD_Track As Integer
    ECD_TrackNumber As Integer
    ECD_Year As String
    ECD_Composer As String
End Type

' Used to send back the ID3 tag to the application
' This is different from id3v2_header and id3v2_tag_frame since this type does not have string lengths defined
' Also some reformatting has been done so usage will be more user friendly
Public Type id3v2_tag
    has_tag As Boolean
    tag_version As String
    artist As String
    title As String
    album As String
    year As String
    comment As String
    comment_language As String
    genre As String
    track As String
    other_tags() As String
    other_tags_cnt As Integer
End Type

' Used to send back the ID3 tag to the application
' This is different from id3v1_file_footer since this type does not have string lengths defined
' Also some reformatting has been done so usage will be more user friendly
Public Type id3v1_tag
    has_tag As Boolean
    artist As String
    title As String
    album As String
    year As String
    has_trackno As Boolean
    trackno As Byte
    comment As String
    genre_id As Byte
    genre_text As String
    speed As Byte
End Type

' For internal use, a structure to read the ASF_File_Properties_Object
Private Type ASF_File_Properties
    fileid_guid As String * 16
    filesize1 As Long ' QuadWORD
    filesize2 As Long ' ^^^^^^^^
    creation_date1 As Long ' QuadWORD
    creation_date2 As Long ' ^^^^^^^^
    data_packets1 As Long ' QuadWORD
    data_packets2 As Long ' ^^^^^^^^
    play_duration1 As Long ' QuadWORD
    play_duration2 As Long ' ^^^^^^^^
    send_duration1 As Long ' QuadWORD
    send_duration2 As Long ' ^^^^^^^^
    preroll1 As Long ' QuadWORD
    preroll2 As Long ' ^^^^^^^^
    flags_raw As Long ' DoubleWORD
    min_packet_size As Long ' DoubleWORD
    max_packet_size As Long ' DoubleWORD
    max_bitrate As Long ' DoubleWORD
End Type

' For internal use, a structure to read ContentDescription sizes
Private Type ASF_CD_Lengths
    title_len As Integer
    author_len As Integer
    copyright_len As Integer
    description_len As Integer
    rating_len As Integer
End Type

' ID3v2 tag frame
Private Type id3v2_tag_frame
    frame_id As String * 4
    framesize As String * 4
    flag_byte1 As Byte
    flag_byte2 As Byte
End Type

' ID3v2 file header
Private Type id3v2_header
    identifier As String * 3
    'major_ver is always 2 and therefor not in the header
    minor_ver As Byte
    revision As Byte
    flags As Byte
    tagsize_byte1 As Byte
    tagsize_byte2 As Byte
    tagsize_byte3 As Byte
    tagsize_byte4 As Byte
End Type

' Used to read the ID3 tag from the file
Private Type id3v1_file_footer
    Ext_Header As String * 4
    Ext_Title As String * 60
    Ext_Artist As String * 60
    Ext_Album As String * 60
    Ext_Speed As Byte
    Ext_Genre As String * 30
    Ext_StartTime As String * 6
    Ext_EndTime As String * 6
    header As String * 3
    SongTitle As String * 30
    artist As String * 30
    album As String * 30
    year As String * 4
    comment As String * 28
    ZeroByte As Byte
    trackno As Byte
    genre As Byte
End Type

''
' Combines two Long values to one variant (for QuadWORDs)
' @param    long     long1      The first value
' @param    long     long2      The second value
' @return   Variant             Both values combined
' @author   Steven Don <www.shdon.com>
Public Function two_longs_to_variant(long1 As Long, long2 As Long) As Double
    Dim ret As Double
    Dim bit31 As Double, bit63 As Double
    
    bit31 = &H8000
    bit31 = bit31 * &H10000
    bit63 = bit31 * bit31
    bit63 = bit63 * 2
    
    'Move long2 into upper 32 bits, excluding sign bit
    ret = (long2 And &H7FFFFFFF)
    ret = ret * &H10000
    ret = ret * &H10000
    
    'Move long1 into lower 31 bits
    ret = ret + (long1 And &H7FFFFFFF)
    
    'Take care of sign bits
    If (long1 < 0) Then ret = ret + bit31
    If (long2 < 0) Then ret = ret + bit63
    
    two_longs_to_variant = ret
End Function


''
' Converts a heximal string to a decimal integer
' Same syntax as the PHP function 'hexdec'
' See also: http://www.php.net/manual/en/function.hexdec.php
' @param    String  hex_string      The heximal string
' @return   Integer                 An integer value
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function hexdec(hex_string As String) As Integer
    hexdec = Val("&h" & hex_string)
End Function

''
' Converts a String to a binary GUID
' @param    string  GUID        The GUID, for example "{01234567-89AB-CDEF-0123-456789ABCDEF}"
' @return   string              A binary string, always 16 bytes
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function GUIDFromString(ByVal GUID As String) As String
    ' The return value will be built in here
    Dim ret As String
    ' Multiple used variables
    Dim s As String, i As Integer
    ' Removes some formatting characters
    GUID = Replace(GUID, "-", "")
    If Left(GUID, 1) = "{" And Right(GUID, 1) = "}" Then GUID = Mid(GUID, 2, Len(GUID) - 2)
    ' The first part
    s = Left(GUID, 8)
    For i = 7 To 1 Step -2
        ret = ret & Chr(hexdec(Mid(s, i, 2)))
    Next i
    ' The second part
    s = Mid(GUID, 9, 4)
    For i = 3 To 1 Step -2
        ret = ret & Chr(hexdec(Mid(s, i, 2)))
    Next i
    ' The third part
    s = Mid(GUID, 13, 4)
    For i = 3 To 1 Step -2
        ret = ret & Chr(hexdec(Mid(s, i, 2)))
    Next i
    ' The forth part
    s = Right(GUID, 16)
    For i = 1 To 15 Step 2
        ret = ret & Chr(hexdec(Mid(s, i, 2)))
    Next i
    
    GUIDFromString = ret
End Function

''
' Reads an ASF (WMA) tag
' @param    String     FileName             The filename of which we need the tags
' @return   asf_tag                         The tag elements
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function asf_get_tag(filename As String) As asf_tag
    ' Variables with multiple uses
    Dim s As String, gs As String * 16, i As Integer
    ' The return value
    Dim ret As asf_tag
    
    ' AFS works with different GUIDs. I wrote them down like this:
    Dim ASF_Header_Object As String * 16
    Dim ASF_Content_Description_Object As String * 16
    Dim ASF_Extended_Content_Description_Object As String * 16
    Dim ASF_File_Properties_Object As String * 16
    ASF_Header_Object = GUIDFromString("75B22630-668E-11CF-A6D9-00AA0062CE6C")
    ASF_Content_Description_Object = GUIDFromString("75B22633-668E-11CF-A6D9-00AA0062CE6C")
    ASF_Extended_Content_Description_Object = GUIDFromString("D2D0A440-E307-11D2-97F0-00A0C95EA850")
    ASF_File_Properties_Object = GUIDFromString("8CABDCA1-A947-11CF-8EE4-00C00C205365")
    
    Dim ff As Integer
    ff = FreeFile
    Open filename For Binary Access Read As #ff
        Get #ff, 1, gs
        If gs = ASF_Header_Object Then
            Dim header_len As Long, header_objects As Long, object_len As Long
            s = String(8, 0): Get #ff, , s: header_len = change_endian(s)      ' Length of the header
            s = String(4, 0): Get #ff, , s: header_objects = change_endian(s)  ' Amount of objects
            s = String(2, 0): Get #ff, , s                                     ' Reserved1 & Reserved 2
            
            ' Loops through all header objects
            Dim ho_i As Integer
            For ho_i = 1 To header_objects
                ' Fetching next GUID
                Get #ff, , gs
                ' Length of the object
                s = String(8, 0): Get #ff, , s: object_len = change_endian(s)
                If gs = ASF_Content_Description_Object Then
                    Dim cd_len As ASF_CD_Lengths
                    ret.CD_Found = True
                    Get #ff, , cd_len
                    s = String(cd_len.title_len, 0): Get #ff, , s: ret.CD_Title = Replace(s, Chr(0), "")
                    s = String(cd_len.author_len, 0): Get #ff, , s: ret.CD_Author = Replace(s, Chr(0), "")
                    s = String(cd_len.copyright_len, 0): Get #ff, , s: ret.CD_Copyright = Replace(s, Chr(0), "")
                    s = String(cd_len.description_len, 0): Get #ff, , s: ret.CD_Description = Replace(s, Chr(0), "")
                    s = String(cd_len.rating_len, 0): Get #ff, , s: ret.CD_Rating = Replace(s, Chr(0), "")
                ElseIf gs = ASF_Extended_Content_Description_Object Then
                    Dim ecd_item_count As Integer
                    ret.ECD_Found = True
                    Get #ff, , ecd_item_count
                    For i = 1 To ecd_item_count
                        Dim ecd_namelen As Integer, ecd_name As String
                        Get #ff, , ecd_namelen
                        ecd_name = String(ecd_namelen, 0): Get #ff, , ecd_name
                        Dim ecd_valtype As Integer, ecd_vallen As Integer, ecd_value As String
                        Get #ff, , ecd_valtype
                        Get #ff, , ecd_vallen
                        ecd_value = String(ecd_vallen, 0): Get #ff, , ecd_value
                        
                        ecd_name = Replace(ecd_name, Chr(0), "")
                        If ecd_valtype = 0 Or ecd_valtype = 1 Then
                            ecd_value = Replace(ecd_value, Chr(0), "")
                            Select Case LCase(ecd_name)
                                Case "wm/genre": ret.ECD_Genre = ecd_value
                                Case "wm/albumtitle": ret.ECD_AlbumTitle = ecd_value
                                Case "wm/year": ret.ECD_Year = ecd_value
                                Case "wm/composer": ret.ECD_Composer = ecd_value
                                Case Else: Rem MsgBox ecd_name & ": " & ecd_value
                            End Select
                        ElseIf ecd_valtype = 3 Then
                            Select Case LCase(ecd_name)
                                Case "wm/track": ret.ECD_Track = Asc(ecd_value)
                                Case "wm/tracknumber": ret.ECD_TrackNumber = Asc(ecd_value)
                                Case Else: Rem MsgBox ecd_name & ": " & Asc(ecd_value)
                            End Select
                        Else
                            Rem MsgBox ecd_valtype & ":" & ecd_name & ": " & Replace(ecd_value, Chr(0), "")
                        End If
                    Next i
                ElseIf gs = ASF_File_Properties_Object Then
                    Dim fp As ASF_File_Properties, pt As Long
                    Get #ff, , fp
                    pt = (two_longs_to_variant(fp.play_duration1, fp.play_duration2) / 10000000) - (two_longs_to_variant(fp.preroll1, fp.preroll2) / 1000)
                    ret.FP_Playtime = pt
                    ret.FP_Filesize = two_longs_to_variant(fp.filesize1, fp.filesize2)
                    ret.FP_Bitrate = (ret.FP_Filesize * 8) / ret.FP_Playtime
                Else
                    ' Skips the data of this unknown block
                    ' The length is minus 24, since the guid and length itself are also counted
                    s = String(object_len - 24, 0): Get #ff, , s
                End If
            Next ho_i
        End If
    Close #ff
    
    asf_get_tag = ret
End Function

''
' When reading a binary file in another endian, this function can convert multiple bytes to a valid number
' @param    string  inp         The bytes as string
' @return   variant             The actual number
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function change_endian(ByVal inp As String, Optional ByVal reverse As Boolean = False) As Variant
    If Len(inp) = 0 Then change_endian = 0: Exit Function
    
    Dim i As Integer, s As String
    If reverse Then
        For i = 1 To Len(inp)
            s = s & zerofill(decbin(Asc(Mid(inp, i, 1))), 8)
        Next i
    Else
        For i = Len(inp) To 1 Step -1
            s = s & zerofill(decbin(Asc(Mid(inp, i, 1))), 8)
        Next i
    End If
    
    change_endian = bindec(s)
End Function

''
' Trims all kind of whitespaces
' The VB6 trim() function only removes spaces, this function also removes null-chars, tabs, returns and linefeeds
' @param    String  txt         The input text
' @return   String              The trimmed output text
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function RealTrim(ByVal txt As String) As String
    Do
        If Len(txt) = 0 Then
            Exit Do
        ElseIf Left(txt, 1) = Chr(0) Or _
           Left(txt, 1) = vbTab Or _
           Left(txt, 1) = " " Or _
           Left(txt, 1) = vbCr Or _
           Left(txt, 1) = vbLf Then
                txt = Right(txt, Len(txt) - 1)
        ElseIf Right(txt, 1) = Chr(0) Or _
           Right(txt, 1) = vbTab Or _
           Right(txt, 1) = " " Or _
           Right(txt, 1) = vbCr Or _
           Right(txt, 1) = vbLf Then
                txt = Left(txt, Len(txt) - 1)
        Else
            Exit Do
        End If
    Loop
    RealTrim = txt
End Function

''
' Makes a binary string from an integer number
' Same syntax as the PHP function 'decbin'
' See also: http://www.php.net/manual/en/function.decbin.php
' @param    Integer number          The decimal value
' @return   String                  A binary presentation of the number (ex.: 00100111)
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function decbin(ByVal number As Integer) As String
    Dim retval As String
    Do Until number = 0
        If (number Mod 2) Then retval = "1" & retval Else retval = "0" & retval
        number = number \ 2
    Loop
    decbin = retval
End Function

''
' Makes an integer number from a binary string
' Same syntax as the PHP function 'bindec'
' See also: http://www.php.net/manual/en/function.bindec.php
' @param    String  binary_string   The binary string (ex.: 00100111)
' @return   Integer                 A decimal presentation of the binary value
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function bindec(binary_string As String) As Long
    Dim i As Integer, pos As Integer, ret As Long
    For i = 1 To Len(binary_string)
        pos = Len(binary_string) - i
        If Mid(binary_string, pos + 1, 1) = "1" Then ret = ret + (2 ^ (i - 1))
    Next i
    bindec = ret
End Function

''
' Completes a string by adding zero characters to the front
' @param    string  value       The input string
' @param    integer length      The length the return value must be
' @param    string  character   Optional, the character that should be used for filling, default: "0"
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function zerofill(ByVal value As String, ByVal length As Integer, Optional ByVal character = "0") As String
    If Len(value) >= length Then zerofill = value: Exit Function
    Dim i As Integer
    Do
        value = character & value
    Loop While Len(value) < length
    zerofill = value
End Function

''
' Reads an ID3v2-tag (to up to 2.3) from a file and returns it
' @param    String     FileName             The filename of which we need the tags
' @return   id3v2_tag                       The tag elements
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function id3v2_get_tag(ByVal filename As String) As id3v2_tag
    ' Some temporal reusable variables
    Dim ff As Long, s As String
    
    ' Some internal values
    Dim id3hdr As id3v2_header, retval As id3v2_tag, tagframe As id3v2_tag_frame
    Dim tagsize As Long, unsynchronisation As Boolean, extended_header As Boolean, experimental_indicator As Boolean

    ff = FreeFile
    Open filename For Binary Access Read As #ff
    Get #ff, 1, id3hdr
    If id3hdr.identifier = "ID3" And id3hdr.minor_ver < 4 Then
        ' Some basic tag information
        retval.has_tag = True
        retval.tag_version = "2." & id3hdr.minor_ver & "." & id3hdr.revision
        
        ' Reading the flags
        s = decbin(Val(id3hdr.flags))
        If Mid(s, 8, 1) = "1" Then unsynchronisation = True
        If Mid(s, 7, 1) = "1" Then extended_header = True
        If Mid(s, 6, 1) = "1" Then experimental_indicator = True
        
        ' Reading the tag size
        s = Left(zerofill(decbin(Val(id3hdr.tagsize_byte1)), 8), 7)     ' The ID3v2 tag size is encoded with four bytes where the most
        s = s & Left(zerofill(decbin(Val(id3hdr.tagsize_byte2)), 8), 7) ' significant bit (bit 7) is set to zero in every byte, making a total
        s = s & Left(zerofill(decbin(Val(id3hdr.tagsize_byte3)), 8), 7) ' of 28 bits. The zeroed bits are ignored, so a 257 bytes long tag is
        s = s & Left(zerofill(decbin(Val(id3hdr.tagsize_byte4)), 8), 7) ' represented as $00 00 02 01.
        
        tagsize = bindec(s)
        
        If extended_header Then
            ' Unsupported so far
            retval.has_tag = False
            id3v2_get_tag = retval
            Close #ff
            Exit Function
        End If
        
        ' The ID3v2 tag size is the size of the complete tag after unsychronisation, including padding,
        ' excluding the header but not excluding the extended header (total tag size - 10).
        Do While Seek(ff) < (tagsize + 10)
            Get #ff, , tagframe
            
            ' Reading the actual value
            s = String(change_endian(tagframe.framesize, True), 0): Get #ff, , s
            
            Select Case UCase(tagframe.frame_id)
                Case "TPE1": retval.artist = RealTrim(s)
                Case "TIT2": retval.title = RealTrim(s)
                Case "TALB": retval.album = RealTrim(s)
                Case "TYER": retval.year = RealTrim(s)
                Case "TCON": retval.genre = RealTrim(s)
                Case "TRCK": retval.track = RealTrim(s)
                Case "COMM"
                    ' First byte: character encoding
                    ' Three bytes: language
                    ' Rest: Zero-byte with the actual value
                    retval.comment_language = Mid(s, 2, 3)
                    retval.comment = RealTrim(Mid(s, 5))
                Case String(4, 0)
                    Exit Do
                Case Else
                    ReDim Preserve retval.other_tags(0 To retval.other_tags_cnt)
                    retval.other_tags(retval.other_tags_cnt) = tagframe.frame_id & s
                    retval.other_tags_cnt = retval.other_tags_cnt + 1
            End Select
        Loop
    End If
    Close #ff
    
    id3v2_get_tag = retval
End Function

''
' Returns the name of a genre number
' @param    Byte       id                   The number of the genre
' @return   String                          The name of the genre
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function id3_genre_name(id As Byte) As String
    Dim genre As String
    Select Case id
        Case 0: genre = "Blues"
        Case 1: genre = "Classic Rock"
        Case 2: genre = "Country"
        Case 3: genre = "Dance"
        Case 4: genre = "Disco"
        Case 5: genre = "Funk"
        Case 6: genre = "Grunge"
        Case 7: genre = "Hip-Hop"
        Case 8: genre = "Jazz"
        Case 9: genre = "Metal"
        Case 10: genre = "New Age"
        Case 11: genre = "Oldies"
        Case 12: genre = "Other"
        Case 13: genre = "Pop"
        Case 14: genre = "R&B"
        Case 15: genre = "Rap"
        Case 16: genre = "Reggae"
        Case 17: genre = "Rock"
        Case 18: genre = "Techno"
        Case 19: genre = "Industrial"
        Case 20: genre = "Alternative"
        Case 21: genre = "Ska"
        Case 22: genre = "Death Metal"
        Case 23: genre = "Pranks"
        Case 24: genre = "Soundtrack"
        Case 25: genre = "Euro-Techno"
        Case 26: genre = "Ambient"
        Case 27: genre = "Trip-Hop"
        Case 28: genre = "Vocal"
        Case 29: genre = "Jazz+Funk"
        Case 30: genre = "Fusion"
        Case 31: genre = "Trance"
        Case 32: genre = "Classical"
        Case 33: genre = "Instrumental"
        Case 34: genre = "Acid"
        Case 35: genre = "House"
        Case 36: genre = "Game"
        Case 37: genre = "Sound Clip"
        Case 38: genre = "Gospel"
        Case 39: genre = "Noise"
        Case 40: genre = "AlternRock"
        Case 41: genre = "Bass"
        Case 42: genre = "Soul"
        Case 43: genre = "Punk"
        Case 44: genre = "Space"
        Case 45: genre = "Meditative"
        Case 46: genre = "Instrumental Pop"
        Case 47: genre = "Instrumental Rock"
        Case 48: genre = "Ethnic"
        Case 49: genre = "Gothic"
        Case 50: genre = "Darkwave"
        Case 51: genre = "Techno-Industrial"
        Case 52: genre = "Electronic"
        Case 53: genre = "Pop-Folk"
        Case 54: genre = "Eurodance"
        Case 55: genre = "Dream"
        Case 56: genre = "Southern Rock"
        Case 57: genre = "Comedy"
        Case 58: genre = "Cult"
        Case 59: genre = "Gangsta"
        Case 60: genre = "Top 40"
        Case 61: genre = "Christian Rap"
        Case 62: genre = "Pop/Funk"
        Case 63: genre = "Jungle"
        Case 64: genre = "Native American"
        Case 65: genre = "Cabaret"
        Case 66: genre = "New Wave"
        Case 67: genre = "Psychadelic"
        Case 68: genre = "Rave"
        Case 69: genre = "Showtunes"
        Case 70: genre = "Trailer"
        Case 71: genre = "Lo-Fi"
        Case 72: genre = "Tribal"
        Case 73: genre = "Acid Punk"
        Case 74: genre = "Acid Jazz"
        Case 75: genre = "Polka"
        Case 76: genre = "Retro"
        Case 77: genre = "Musical"
        Case 78: genre = "Rock & Roll"
        Case 79: genre = "Hard Rock"
        Case 80: genre = "Folk"
        Case 81: genre = "Folk-Rock"
        Case 82: genre = "National Folk"
        Case 83: genre = "Swing"
        Case 84: genre = "Fast Fusion"
        Case 85: genre = "Bebob"
        Case 86: genre = "Latin"
        Case 87: genre = "Revival"
        Case 88: genre = "Celtic"
        Case 89: genre = "Bluegrass"
        Case 90: genre = "Avantgarde"
        Case 91: genre = "Gothic Rock"
        Case 92: genre = "Progressive Rock"
        Case 93: genre = "Psychedelic Rock"
        Case 94: genre = "Symphonic Rock"
        Case 95: genre = "Slow Rock"
        Case 96: genre = "Big Band"
        Case 97: genre = "Chorus"
        Case 98: genre = "Easy Listening"
        Case 99: genre = "Acoustic"
        Case 100: genre = "Humour"
        Case 101: genre = "Speech"
        Case 102: genre = "Chanson"
        Case 103: genre = "Opera"
        Case 104: genre = "Chamber Music"
        Case 105: genre = "Sonata"
        Case 106: genre = "Symphony"
        Case 107: genre = "Booty Bass"
        Case 108: genre = "Primus"
        Case 109: genre = "Porn Groove"
        Case 110: genre = "Satire"
        Case 111: genre = "Slow Jam"
        Case 112: genre = "Club"
        Case 113: genre = "Tango"
        Case 114: genre = "Samba"
        Case 115: genre = "Folklore"
        Case 116: genre = "Ballad"
        Case 117: genre = "Power Ballad"
        Case 118: genre = "Rhythmic Soul"
        Case 119: genre = "Freestyle"
        Case 120: genre = "Duet"
        Case 121: genre = "Punk Rock"
        Case 122: genre = "Drum Solo"
        Case 123: genre = "A capella"
        Case 124: genre = "Euro-House"
        Case 125: genre = "Dance Hall"
        Case Else: genre = "Unknown"
    End Select
    id3_genre_name = genre
End Function

''
' Reads an ID3v1-tag from a file and returns it
' Supports ID3 version 1 with TAG+
' @param    String     FileName             The filename of which we need the tags
' @return   id3v1_tag                       The tag elements
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function id3v1_get_tag(ByVal filename As String) As id3v1_tag
    On Error GoTo id3v1_get_tag_error
    
    ' At default we don't have a tag
    id3v1_get_tag.has_tag = False

    Dim file_footer As id3v1_file_footer
    Dim ff As Long

    ff = FreeFile
    Open filename For Binary Access Read As #ff
    Get #ff, LOF(ff) - (Len(file_footer) - 1), file_footer
    Close #ff
    
    ' Not a valid ID3v1-tag
    If file_footer.header <> "TAG" Then Exit Function
    
    With id3v1_get_tag
        .has_tag = True
        .artist = Trim(Replace(file_footer.artist, Chr(0), ""))
        .title = Trim(Replace(file_footer.SongTitle, Chr(0), ""))
        .album = Trim(Replace(file_footer.album, Chr(0), ""))
        .year = Trim(Replace(file_footer.year, Chr(0), ""))
        .genre_id = file_footer.genre
        
        If file_footer.ZeroByte = 0 Then
            ' Track number is specified, comment can only contain 28 characters
            .has_trackno = True
            .trackno = file_footer.trackno
            .comment = Trim(Replace(file_footer.comment, Chr(0), ""))
        Else
            ' Comment is 30 characters long and no track number is specified
            .has_trackno = False
            .comment = Trim(Replace(file_footer.comment & Chr(file_footer.ZeroByte) & Chr(file_footer.trackno), Chr(0), ""))
        End If
        
        ' The tag can be extended according to the specifications
        If file_footer.Ext_Header = "TAG+" Then
            .artist = .artist & Trim(Replace(file_footer.Ext_Artist, Chr(0), ""))
            .title = .title & Trim(Replace(file_footer.Ext_Title, Chr(0), ""))
            .album = .album & Trim(Replace(file_footer.Ext_Artist, Chr(0), ""))
            .genre_text = Trim(Replace(file_footer.Ext_Genre, Chr(0), ""))
            .speed = file_footer.Ext_Speed
        Else
            .genre_text = id3_genre_name(.genre_id)
        End If
    End With
    
    Exit Function

id3v1_get_tag_error:
    Close #ff
End Function

''
' Reads the id3 or asf tags from an audio file
' @param    String      filename        The audio file
' @return   audio_tag                   A generic type with a few entries that exist in most tags
' @author   Stefan Thoolen <mail@stefanthoolen.nl>
Public Function get_audio_tag(filename As String) As audio_tag
    Dim id3v1 As id3v1_tag, id3v2 As id3v2_tag, asf As asf_tag
    
    id3v1 = id3v1_get_tag(filename)
    If id3v1.has_tag Then
        With get_audio_tag
            .type = "ID3v1.1.0"
            .album = id3v1.album
            .artist = id3v1.artist
            .comment = id3v1.comment
            .genre = id3v1.genre_text
            .title = id3v1.title
            .has_track = id3v1.has_trackno
            .track = id3v1.trackno
            .year = id3v1.year
        End With
        Exit Function
    End If
    
    id3v2 = id3v2_get_tag(filename)
    If id3v2.has_tag Then
        With get_audio_tag
            .type = "ID3v" & id3v2.tag_version
            .album = id3v2.album
            .artist = id3v2.artist
            .comment = id3v2.comment
            .genre = id3v2.genre
            .title = id3v2.title
            If id3v2.track <> "" Then .has_track = True
            .track = Val(id3v2.track)
            .year = id3v2.year
        End With
        Exit Function
    End If
    
    asf = asf_get_tag(filename)
    If asf.CD_Found Then
        With get_audio_tag
            .type = "ASF"
            .album = asf.ECD_AlbumTitle
            .artist = asf.CD_Author
            .comment = asf.CD_Description
            .genre = asf.ECD_Genre
            .title = asf.CD_Title
            If asf.ECD_Track > 0 Then .has_track = True: .track = asf.ECD_Track
            If asf.ECD_TrackNumber > 0 Then .has_track = True: .track = asf.ECD_TrackNumber
            .year = asf.ECD_Year
        End With
        Exit Function
    End If
End Function