ID3 tags library :)

BlitzMax Modules Forums/Brucey's Modules/ID3 tags library :)

Hi Brucey :)

Just an idea :)

http://www.3delite.hu/Object%20Pascal%20Developer%20Resources/id3v2library.html

Others :
http://www.3delite.hu/Object%20Pascal%20Developer%20Resources/download.html#id3


Regards

It's Windows-only, unfortunately...

As part of the "Make a Mod in an Evening" festival which was held here today, you may be interested in a new module in the maxmods SVN repository : BaH.TagLib

It supports both ID3v1 and ID3v2 for MP3 files, Ogg Vorbis comments and ID3 tags and Vorbis comments in FLAC, MPC, Speex, WavPack and TrueAudio files.... amongst others.

Sample output from the included example :
******************** "Pink Floyd - 03 Time.mp3" ********************
-- TAG --
title   - "03 Time"
artist  - "Pink Floyd"
album   - "Dark Side Of The Moon"
year    - "0"
comment - ""
track   - "3"
genre   - "Psychedelic Rock"
-- AUDIO --
bitrate     - 128
sample rate - 44100
channels    - 2
length      - 7:05


Only the generic API has been implemented for now, which provides the basic information as above.

Currently tested on Windows and Mac.
License is MPL.

<edit>
NOTE : This requires the lastest SVN-version of BlitzMax, since it uses some of the new Unicode conversion functions.
(At some point I will also be converting all of my own-module unicode functions to use these "official" ones instead, but I'll probably hold off until we get a 1.32 release)
</edit>

As usual, comments, critique and suggestions welcome.

Enjoy.

:o)

Heh, I saw this post, then it seemed like only an hour after your first response that the svn had a new treat. Nice work!

Edit: nice choice of music =p

I can't get it to compile, I get this error.
"Compile Error: Identifier 'bbStringToUTF8String' not found"
Do I need any other module or something else? :)

Thanks :)

Hmm.. Do you have MinGW installed? Was this module built for the SVN version? (I think it has UTF string support now)

Yes I have MinGW installed etc, I downloaded the code from the latest SVN rev :P

same problem here. i download bruceys svn stuff every day.

"Compile Error: Identifier 'bbStringToUTF8String' not found"

Ah... yes, you also need to use the latest BlitzMax SVN too, since I'm using BlitzMax's new Unicode friendly functions here - rather than having to duplicate my own as I have done for most of my modules in the past.

If you want to "hack" a fix without having to get all that stuff, you could, in theory add the following code to the bottom of BlitzMax/mod/brl.mod/blitz.mod/blitz.bmx :
Extern "c"
	Function strlen_( str:Byte Ptr )="strlen"
End Extern

' converts a UTF character array from byte-size characters to short-size characters
' based on the TextStream UTF code...
Function bbStringFromUTF8String:String(s:Byte Ptr)
	If s Then
		Local l:Int = strlen_(s)
		Local b:Short[] = New Short[l]
		Local bc:Int = -1, c:Int, d:Int, e:Int, f:Int
		For Local i:Int = 0 Until l
			bc:+1
			c = s[i]
			If c<128 
				b[bc] = c
				Continue
			End If
			i:+1
			d=s[i]
			If c<224 
				b[bc] = ((c & 31) Shl 6) | (d & 63)
				Continue
			End If
			i:+1
			e = s[i]
			If c < 240 
				b[bc] = ((c & 15) Shl 12) | ((d & 63) Shl  6) | (e & 63)
				Continue
			End If
			i:+1
			f = s[i]
			Local v:Int = (((c & 7) Shr 18) | ((d & 63) Shl 12) | ((e & 63) Shl  6) | (f & 63))
			If v < 65536 Then
				b[bc] = v
				Continue
			End If
			' requires 2 16-bit characters!
			b[bc] = ((v - 65536) / 1024) + 55296
			bc:+1
			b[bc] = (v Mod 1024) + 56320
		Next
		Return String.fromshorts(b, bc + 1)
	End If
	
	Return ""
	
End Function

' converts a Max short-based String to a byte-based UTF-8 String.
' based on the TextStream UTF code...
Function bbStringToUTF8String:String(text:String)
	If Not text Then
		Return ""
	End If
	
	Local l:Int = text.length
	If l = 0 Then
		Return ""
	End If
	
	Local count:Int = 0
	Local s:Byte[] = New Byte[l * 4] ' max possible is 4 x original size.
	
	For Local i:Int = 0 Until l
		Local char:Int = text[i]

		If char < 128 Then
			s[count] = char
			count:+ 1
			Continue
		Else If char<2048
			s[count] = char/64 | 192
			count:+ 1
			s[count] = char Mod 64 | 128
			count:+ 1
			Continue
		Else If char < 55296 Or char > 56319 ' not a surrogate pair?
			s[count] =  char/4096 | 224
			count:+ 1
			s[count] = char/64 Mod 64 | 128
			count:+ 1
			s[count] = char Mod 64 | 128
			count:+ 1
			Continue
		Else
			' this character built from pair of 16-bit characters 
			If i < (l-1) Then ' valid character?
				i:+1
				char = ((char - $D800) Shl 10) + (text[i] - $DC00) + $10000
			End If
		
			s[count] = char/262144 | 240
			count:+ 1
			s[count] = char/4096 Mod 64 | 128
			count:+ 1
			s[count] = char/64 Mod 64 | 128
			count:+1
			s[count] = char Mod 64 | 128
			count:+1
			Continue
		EndIf
		
	Next

	Return String.fromBytes(s, count)
End Function


Oh alright, I'll go get the latest BlitzMax SVN rev, Thanks Brucey :)

That is the SVN I was referring to :D

Oh alright, I'll go get the latest BlitzMax SVN rev, Thanks Brucey :)

I realise it's not ideal to rely on "unreleased" additions to BlitzMax, but I if it makes my life a little easier, it's not a terribly bad thing... and I figure many folks who might dabble with my SVN-only modules may be likely to have the SVN-only BlitzMax updates too :-p

Apologies for the confusion anyway... I'll make a note in the post above :-)

Hi

The start for ID3TAG V1, for ID3TAG V2 i don"'t understand very well the 'frame' stuff

Const DllName:String = "ID3v2Library.dll"
Local DllHandle = LoadLibraryA(DllName)

If DLLhandle = 0 Then
	Notify "Unable to initialize ID3v2Library.dll"
	End
EndIf


Global ID3v1_Create:Int()"Win32" = GetProcAddress(DllHandle , "ID3v1_Create")
Global ID3v1_Free:Int(Tag:Int)"Win32" = GetProcAddress(DllHandle , "ID3v1_Free")
Global ID3v1_Load(Tag:Int , FileName:Byte Ptr)"Win32" = GetProcAddress(DllHandle , "ID3v1_Load") 
Global ID3v1_Save(Tag:Int , FileName:Byte Ptr)"Win32" = GetProcAddress(DllHandle , "ID3v1_Save") 
Global ID3v1_Kill(Tag:Int)"Win32" = GetProcAddress(DllHandle , "ID3v1_Kill") 

Global ID3v1_GetTitle$z(Tag:Int)"Win32"= GetProcAddress(DllHandle,"ID3v1_GetTitle")
Global ID3v1_GetArtist$z(Tag:Int)"Win32" = GetProcAddress(DllHandle , "ID3v1_GetArtist") 
Global ID3v1_GetAlbum$z(Tag:Int)"Win32"= GetProcAddress(DllHandle,"ID3v1_GetAlbum")
Global ID3v1_GetYear$z(Tag:Int)"Win32" = GetProcAddress(DllHandle , "ID3v1_GetYear")
Global ID3v1_GetComment$z(Tag:Int)"Win32" = GetProcAddress(DllHandle , "ID3v1_GetComment")
Global ID3v1_GetTrack:Int(Tag:Int)"Win32" = GetProcAddress(DllHandle , "ID3v1_GetTrack")
Global ID3v1_GetGenre:Int(Tag:Int)"Win32" = GetProcAddress(DllHandle , "ID3v1_GetGenre")


Global ID3v1_SetTitle:Int(Tag:Int, Title:Byte Ptr)"Win32"= GetProcAddress(DllHandle,"ID3v1_SetTitle")
Global ID3v1_SetArtist:Int(Tag:Int, Artist:Byte Ptr)"Win32"= GetProcAddress(DllHandle,"ID3v1_SetArtist")
Global ID3v1_SetAlbum:Int(Tag:Int, Album:Byte Ptr)"Win32"= GetProcAddress(DllHandle,"ID3v1_SetAlbum")
Global ID3v1_SetYear:Int(Tag:Int, Year:Byte Ptr)"Win32"= GetProcAddress(DllHandle,"ID3v1_SetYear")
Global ID3v1_SetComment:Int(Tag:Int, Comment:Byte Ptr)"Win32"= GetProcAddress(DllHandle,"ID3v1_SetComment")
Global ID3v1_SetTrack:Int(Tag:Int, Track:Int)"Win32"= GetProcAddress(DllHandle,"ID3v1_SetTrack")
Global ID3v1_SetGenre:Int(Tag:Int, Genre:Int)"Win32"= GetProcAddress(DllHandle,"ID3v1_SetGenre")



'# ID3v2_Create(): Pointer; stdcall;
'# ID3v2_Free(Tag: Pointer): Boolean; stdcall;
'# ID3v2_Kill(Tag: Pointer): Integer; stdcall;
'# ID3v2_Load(Tag: Pointer; FileName: PChar): Integer; stdcall;
'# ID3v2_Loaded(Tag: Pointer): Boolean; stdcall;
'# ID3v2_Save(Tag: Pointer; FileName: PChar): Integer; stdcall;
'# ID3v2_GetAsciiText(Tag: Pointer; FrameName: PChar): PChar; stdcall;
'# ID3v2_SetAsciiText(Tag: Pointer; FrameName, Text: PChar): Boolean; stdcall;
'# ID3v2_FrameCount(Tag: Pointer): Cardinal; stdcall;
'# ID3v2_GetFrameData(Tag: Pointer; FrameIndex: Cardinal; Var Data: TStream): Integer; stdcall;
'# ID3v2_SetFrameData(Tag: Pointer; FrameIndex: Cardinal; Var Data: TStream): Integer; stdcall;
'# ID3v2_CheckFrameExists(Tag: Pointer; FrameID: PChar): Integer; stdcall;
'# ID3v2_GetFrameID(Tag: Pointer; FrameIndex: Cardinal): PChar; stdcall;
'# ID3v2_DeleteFrame(Tag: Pointer; FrameIndex: Cardinal): Boolean; stdcall;
'# ID3v2_CreateFrame(Tag: Pointer; FrameID: PChar): Cardinal; stdcall;
'# ID3v2_CompressFrame(Tag: Pointer; FrameIndex: Cardinal): Boolean; stdcall;
'# ID3v2_DeCompressFrame(Tag: Pointer; FrameIndex: Cardinal): Boolean; stdcall;
'# ID3v2_AddAlbumPicture(Tag: Pointer; PictureFileName: PChar; PictureFileFormat: Cardinal = 0): Boolean; stdcall;
'# ID3v2_GetAlbumPicture(Tag: Pointer; FrameIndex: Cardinal; PictureFileName: PChar): Cardinal; stdcall;
'# ID3v2_GetAlbumPictureStream(Tag: Pointer; FrameIndex: Cardinal; PictureStream: TStream): Cardinal; stdcall;
'# ID3v2_SyncFrame(Tag: Pointer; FrameIndex: Cardinal): Integer; stdcall;
'# ID3v2_UnSyncFrame(Tag: Pointer; FrameIndex: Cardinal): Integer; stdcall;
'# ID3v2_SetCOMM(Tag: Pointer; Comment, Description: PChar): Integer; stdcall;
'# ID3v2_GetCOMM(Tag: Pointer): PChar; stdcall;
'# ID3v2_SetWXXX(Tag: Pointer; URL: PChar): Integer; stdcall;
'# ID3v2_GetWXXX(Tag: Pointer): PChar; stdcall;
'# ID3v2_GetFrameDataP(Tag: Pointer; FrameIndex: Cardinal; Var Data: Pointer; Var Length: Cardinal): Integer; stdcall;
'# ID3v2_SetFrameDataP(Tag: Pointer; FrameIndex: Cardinal; Data: Pointer; Length: Cardinal): Integer; stdcall;
'# ID3v2_GetFrameSize(Tag: Pointer; FrameIndex: Cardinal): Cardinal; stdcall; 


Function ID3v1_GetGenreString:String(Number:Int) 
	Select Number
	
	Case 0
		Return "Blues"
	Case 1
		Return "Classic Rock"
	Case 2
		Return "Country"
	Case 3
		Return "Dance"
	Case 4
		Return "Disco"
	Case 5
		Return "Funk"
	Case 6
		Return "Grunge"
	Case 7
		Return "Hip-Hop"
	Case 8
		Return "Jazz"
	Case 9
		Return "Metal"
	Case 10
		Return "New Age"
	Case 11
		Return "Oldies"
	Case 12
		Return "Other"
	Case 13
		Return "Pop"
	Case 14
		Return "R&B"
	Case 15
		Return "Rap"
	Case 16
		Return "Reggae"
	Case 17
		Return "Rock"
	Case 18
		Return "Techno"
	Case 19
		Return "Industrial"
	Case 20
		Return "Alternative"
	Case 21
		Return "Ska"
	Case 22
		Return "Death Metal"
	Case 23
		Return "Pranks"
	Case 24
		Return "Soundtrack"
	Case 25
		Return "Euro-Techno"
	Case 26
		Return "Ambient"
	Case 27
		Return "Trip-Hop"
	Case 28
		Return "Vocal"
	Case 29
		Return "Jazz+Funk"
	Case 30
		Return "Fusion"
	Case 31
		Return "Trance"
	Case 32
		Return "Classical"
	Case 33
		Return "Instrumental"
	Case 34
		Return "Acid"
	Case 35
		Return "House"
	Case 36
		Return "Game"
	Case 37
		Return "Sound Clip"
	Case 38
		Return "Gospel"
	Case 39
		Return "Noise"
	Case 40
		Return "Alternative Rock"
	Case 41
		Return "Bass"
	Case 43
		Return "Punk"
	Case 44
		Return "Space"
	Case 45
		Return "Meditative"
	Case 46
		Return "Instrumental Pop"
	Case 47
		Return "Instrumental Rock"
	Case 48
		Return "Ethnic"
	Case 49
		Return "Gothic"
	Case 50
		Return "Darkwave"
	Case 51
		Return "Techno-Industrial"
	Case 52
		Return "Electronic"
	Case 53
		Return "Pop-Folk"
	Case 54
		Return "Eurodance"
	Case 55
		Return "Dream"
	Case 56
		Return "Southern Rock"
	Case 57
		Return "Comedy"
	Case 58
		Return "Cult"
	Case 59
		Return "Gangsta"
	Case 60
		Return "Top 40"
	Case 61
		Return "Christian Rap"
	Case 62
		Return "Pop/Funk"
	Case 63
		Return "Jungle"
	Case 64
		Return "Native US"
	Case 65
		Return "Cabaret"
	Case 66
		Return "New Wave"
	Case 67
		Return "Psychadelic"
	Case 68
		Return "Rave"
	Case 69
		Return "Showtunes"
	Case 70
		Return "Trailer"
	Case 71
		Return "Lo-Fi"
	Case 72
		Return "Tribal"
	Case 73
		Return "Acid Punk"
	Case 74
		Return "Acid Jazz"
	Case 75
		Return "Polka"
	Case 76
		Return "Retro"
	Case 77
		Return "Musical"
	Case 78
		Return "Rock & Roll"
	Case 79
		Return "Hard Rock"
	Case 80
		Return "Folk"
	Case 81
		Return "Folk-Rock"
	Case 82
		Return "National Folk"
	Case 83
		Return "Swing"
	Case 84
		Return "Fast Fusion"
	Case 85
		Return "Bebob"
	Case 86
		Return "Latin"
	Case 87
		Return "Revival"
	Case 88
		Return "Celtic"
	Case 89
		Return "Bluegrass"
	Case 90
		Return "Avantgarde"
	Case 91
		Return "Gothic Rock"
	Case 92
		Return "Progressive Rock"
	Case 93
		Return "Psychedelic Rock"
	Case 94
		Return "Symphonic Rock"
	Case 95
		Return "Slow Rock"
	Case 96
		Return "Big Band"
	Case 97
		Return "Chorus"
	Case 98
		Return "Easy Listening"
	Case 99
		Return "Acoustic"
	Case 100
		Return "Humour"
	Case 101
		Return "Speech"
	Case 102
		Return "Chanson"	
	Case 103
		Return "Opera"
	Case 104
		Return "Chamber Music"
	Case 105
		Return "Sonata"
	Case 106
		Return "Symphony"
	Case 107
		Return "Booty Bass"
	Case 108
		Return "Primus"
	Case 109
		Return "Porn Groove"
	Case 110
		Return "Satire"
	Case 111
		Return "Slow Jam"
	Case 112
		Return "Club"
	Case 113
		Return "Tango"
	Case 114
		Return "Samba"
	Case 115
		Return "Folklore"
	Case 116
		Return "Ballad"
	Case 117
		Return "Power Ballad"
	Case 118
		Return "Rhytmic Soul"
	Case 119
		Return "Freestyle"
	Case 120
		Return "Duet"
	Case 121
		Return "Punk Rock"
	Case 122
		Return "Drum Solo"
	Case 123
		Return "Acapella"
	Case 124
		Return "Euro-House"
	Case 125
		Return "Dance Hall"
	Case 126
		Return "Goa"
	Case 127
		Return "Drum & Bass"
	Case 128
		Return "Club-House"
	Case 129
		Return "Hardcore"
	Case 130
		Return "Terror"
	Case 131
		Return "Indie"
	Case 132
		Return "BritPop"
	Case 133
		Return "Negerpunk"
	Case 134
		Return "Polsk Punk"
	Case 135
		Return "Beat"
	Case 136
		Return "Christian Gangsta"
	Case 137
		Return "Heavy Metal"
	Case 138
		Return "Black Metal"
	Case 139
		Return "Crossover"
	Case 140
		Return "Contemporary C"
	Case 141
		Return "Christian Rock"
	Case 142
		Return "Merengue"
	Case 143
		Return "Salsa"
	Case 144
		Return "Thrash Metal"
	Case 145
		Return "Anime"
	Case 146
		Return "JPop"
	Case 147
		Return "SynthPop"
	Default 
		Return "Unknown"
	End Select
	
End Function



example :

Include "Inc_ID3V2.bmx"

Local File:String= "test.mp3"

Local MyTag:Int=ID3v1_Create()

Print "Loading >" + ID3v1_Load(MyTag , File)
Print "Title >" + ID3v1_GetTitle(MyTag) 
Print "Artist >"+ ID3v1_GetArtist(MyTag)
Print "Album >"+ ID3v1_GetAlbum(MyTag)
Print "Year >"+ ID3v1_GetYear(MyTag)
Print "Comment >"+ ID3v1_GetComment(MyTag)
Print "Track >"+ ID3v1_GetTrack(MyTag)
Print "Genre  >"+ ID3v1_GetGenreString(ID3v1_GetGenre(MyTag))

'D3v1_SetTitle(MyTag , "Runaway Girl")
'D3v1_SetComment(MyTag , "Ceci est un comment")
'D3v1_Save(MyTag , File)


If you use BaH.TagLib, you won't need an external DLL, as the library is compiled into your app (MPL instead of LGPL).

As for the 'frame' stuff, I'll get round to implementing that in the module too. Might be nice to be able to extract album covers from the files :-)

I'll take a look :) Many thanks Brucey :)

It appears that I can't load tags with non-english letters, Is there anything I can do about that? :)

Are you getting any specific errors?
It should be able to handle conversions on-the-fly.
(not that I've tested this of course...)

I just tested this by adding some cyrillic to the comment field of an mp3, and loaded back using the example.
On the console it prints unreadable characters, but if I show the text using Notify, it looks fine - because the IDE's console isn't wide-char friendly. (or perhaps Print isn't....)

So, as far as I am concerned, it is working as expected.
But, I am always ready to be shown otherwise ;-)

I'm loading MP3s with 'ÅÄÖ' these characters in the title, but the title returns null. And also if I tried loading a file with any of these characters in the filepath, the file couldn't get read. (Using TTLFileRef.Create( filepath ).
So it's not just that the console prints the characters a bit odd :)

Okay, I've committed a fix Win32 which lets you open files with non-ASCII path/filenames.

(Yes... you'd think after my crusade to get Unicode files working in core BlitzMax I'd check it worked in my modules too ;-)

Hehe, Thanks a bunch Brucey! :D

I seem to get some random crashes using Release mode with this module, When I'm using debug mode it works just fine.

Here's the error message:
signal!:22
pure virtual method called
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
Received Signal


Any idea on what might cause this? :)

Oh, I didn't release the file after using it.
Also, Rename the Delete method to Remove or something, cause Delete were causing some problems with BlitzMax :P

Nothing off the top of my head, although "pure virtual method called" indicates that something may be called in the library that shouldn't be.

There isn't much of the API that I'm currently using.

Any idea what function you might be calling at the time, or if it is a specific file/type?

If you didn't see, I found the problem one post up :)

Ooops... missed that.. I answered without refreshing, after checking the code.

Oh, I didn't release the file after using it.

Ah... I'll add a Free() method which you can call to release the resource, and remember once it is freed related tag objects etc will be invalidated (by the library, BlitzMax will clean up the references itself when the GC picks them up).

Okay, I've added the Free() method to TTLFileRef. You can call this when you are done with the file.

If you don't call it, the file will be freed eventually, once the object goes out of scope and is GC'd, but that time is undetermined...