How to add Meta-Information support to your Winamp input plug-in

With Winamp 2.90 appeared the ability to deal with meta-information (usually stored in tags such as ID3 tags). This new feature, currently used by the media library but available for general plug-ins developers, is implemented inside input plug-ins. Nullsoft team kept the old interface but added a couple of exported functions do deal with meta information. The purpose of this article is an attempt to describe these undocumented functions.
Update
There is a high probability that this article contains some mistakes. Feel free to drop me a line if you find one.

First of all, you should know that the new 2.90 input plug-ins are fully backward compatible with the old interface. If you’re familiar with the structure In_Module and its associated exported method In_Module* winampGetInModule2() (see Input SDK), the good news is that there’s absolutly no change! As a matter of fact, input plug-ins using the old fashioned interface still work on Winamp 2.90 and above, and new plugins able to deal with meta data still work with old Winamp versions. So keep working with the old SDK, you’ll just have to add some extra functions I’m going to discuss below.

Getting meta information

The first function we’re looking for is how Winamp get a specific field? The answer is simple, he makes a call to this exported function:

__declspec(dllexport) int winampGetExtendedFileInfo(
        char* filename,
        char* metadata,
        char* ret,
        int retlen
);

If a plug-in don’t export this function, well that means Winamp is dealing with an old fashioned plug-in. To understand how this function work, lets have an example of how Winamp calls it :

char buffer[256];
if(winampGetExtendedFileInfo(“C:\\test.mp3”,”TITLE”,buffer,256)==0){
        //we have the artist stored in buffer
}else{
        //there’s something wrong
}

So the first parameter is the full path to a filename, the second one is the field Winamp is querying, the third one is a pointer to a buffer which will receive the requested field and finally the last parameter is the length of the buffer. The returned is value should be 0 if we’re able to answer, or a value different of zero if something wrong happened such as an invalid filename, a missing tag or anything else.

Currently, the media library uses the following metadata names : “TYPE”, “LENGTH”, “ARTIST”, “TITLE”, “ALBUM”, “COMMENT”, “TRACKNO”, “GENRE” and “YEAR”. As we can see, the API has been designed so that this list can be expanded in future.

Finally, remember that there’s no doubt that there will be successive calls to your function using the same filename. Most of the time, you’ll receive one call asking for the title, then another requesting the artist, and so on… So that’s a great idea to use a buffering mechanism. Here is an example of how to implement this :

static char* getfilename[MAX_PATH]=””;
static char* gettype[2]=”0”; // “0” for audio or “1” for video
static char* getlength[256]=”-1”; //in seconds, “-1” if unknown
static char* getartist[256]=””;
static char* gettitle[256]=””;
static char* getalbum[256]=””;
static char* getcomment[256]=””;
static char* gettrackno[3]=””;
static char* getgenre[256]=””;
static char* getyear[5]=””;

__declspec(dllexport) int winampGetExtendedFileInfo(
        char* filename,
        char* metadata,
        char* ret,
        int retlen
){
        if(strcmp(filename,getfilename)){
                //if we can open and read the file
                //fill in our get** variables with tag information
                strcpy(getfilename,filename);
        //else
                return 1;
        }
        if(!strcmp(metadata,”TYPE”)){
                strncpy(ret,gettype,retlen);
                return 0;
        }
        if(!strcmp(metadata,”LENGTH”)){
                strncpy(ret,getlength,retlen);
                return 0;
        }
        if(!strcmp(metadata,”ARTIST”)){
                strncpy(ret,getartiste,retlen);
                return 0;
        }
        if(!strcmp(metadata,”TITLE”)){
                strncpy(ret,gettitle,retlen);
                return 0;
        }
        if(!strcmp(metadata,”ALBUM”)){
                strncpy(ret,getalbum,retlen);
                return 0;
        }
        if(!strcmp(metadata,”COMMENT”)){
                strncpy(ret,getcomment,retlen);
                return 0;
        }
        if(!strcmp(metadata,”TRACKNO”)){
                strncpy(ret,gettrackno,retlen);
                return 0;
        }
        if(!strcmp(metadata,”GENRE”)){
                strncpy(ret,getgenre,retlen);
                return 0;
        }
        if(!strcmp(metadata,”YEAR”)){
                strncpy(ret,getyear,retlen);
                return 0;
        }
        return 1;
}

Here is for the principle, however I can’t give you a full source code for handling your own tags!!!

Setting meta information

Now that getting meta information doesn’t have secret for us any more, lets try to set meta data! To achieve this, Nullsoft has defined two functions like this :

__declspec(dllexport) int winampSetExtendedFileInfo(
        char* filename,
        char* metadata,
        char* value,
);

__declspec(dllexport) int winampWriteExtendedFileInfo();

These functions look very similar to the get function, and if you have understood the buffering mechanism, you should be able to deduce the behaviour of these! Basically, Winamp makes successive calls to the first function to set the various fields whereas the second function write physically the modifications to the file. Above is an example of how Winamp set meta information :

winampSetExtendedFileInfo(“C:\\test.mp3”,”ARTIST”,”Evanescence”);
winampSetExtendedFileInfo(“C:\\test.mp3”,”TITLE”,”Hello”);
winampSetExtendedFileInfo(“C:\\test.mp3”,”ALBUM”,”Fallen”);
winampSetExtendedFileInfo(“C:\\test.mp3”,”LENGTH”,”220”);
winampSetExtendedFileInfo(“C:\\test.mp3”,”YEAR”,”2003”);
if(winampWriteExtendedFileInfo()){
        //popup an error box
}

Is it really necessary to develop that the first parameter passes the filename, the second one the field to be modified and the last one the new value to be assigned? Well, that’s done now so just add that it returns 0 when all goes fine and something else otherwise! In a way similar to the get function, I give you a simplified implementation that may make a starting point :

static char* setfilename[MAX_PATH]=””;
static char* settype[2]=”0”; // “0” for audio or “1” for video
static char* setlength[256]=”-1”; //in seconds, “-1” if unknown
static char* setartist[256]=””;
static char* settitle[256]=””;
static char* setalbum[256]=””;
static char* setcomment[256]=””;
static char* settrackno[3]=””;
static char* setgenre[256]=””;
static char* setyear[5]=””;

__declspec(dllexport) int winampSetExtendedFileInfo(
        char* filename,
        char* metadata,
        char* value,
){
        if(strcmp(filename,setfilename)){
                strcpy(setfilename,filename);
                //we reset all our attributes
                strcpy(settype,”0”);
                strcpy(setlength,”-1”);
                strcpy(setartist,””);
                strcpy(settitle,””);
                strcpy(setalbum,””);
                strcpy(setcomment,””);
                strcpy(settrackno,””);
                strcpy(setgenre,””);
                strcpy(setyear,””);
        }
        if(!strcmp(metadata,”TYPE”)){
                strcpy(settype,value);
                return 0;
        }
        if(!strcmp(metadata,”LENGTH”)){
                strcpy(setlength,value);
                return 0;
        }
        if(!strcmp(metadata,”ARTIST”)){
                strcpy(setartist,value);
                return 0;
        }
        if(!strcmp(metadata,”TITLE”)){
                strcpy(settitle,value);
                return 0;
        }
        if(!strcmp(metadata,”ALBUM”)){
                strcpy(setalbum,value);
                return 0;
        }
        if(!strcmp(metadata,”COMMENT”)){
                strcpy(setcomment,value);
                return 0;
        }
        if(!strcmp(metadata,”TRACKNO”)){
                strcpy(settrackno,value);
                return 0;
        }
        if(!strcmp(metadata,”GENRE”)){
                strcpy(setgenre,value);
                return 0;
        }
        if(!strcmp(metadata,”YEAR”)){
                strcpy(setyear,value);
                return 0;
        }
        return 1;
}

__declspec(dllexport)int winampWriteExtendedFileInfo(){
        if(strcmp(setfilename,””)){
                //if we can open and write to the file then
                if(strcmp(setlength,”-1”)){
                        //write setlength to the tag part of the file
                }
                if(strcmp(setartist,””)){
                        //write setartist to the tag part of the file
                }
                //to be continued...
                return 0;
        }
        return 1;
}

Yes, that’s all. Now the hardest work is in your hands : to write the bridge between this interface and your file format.

Conclusion

You’re now at the end of my paper and you’re thinking “great, that gonna be a child game to add this feature to my plug-in”. But don’t be so enthusiastic! I wrote this paper with the help of two things : the new published IPC codes and the depends utility. This means that all the described behaviours come from my imagination. I didn’t get some extra documentation from Nullsoft, nor I wrote any testing code. Hopefully I’m close to the reality, but I can’t certify it. Have a try and tell me back how good or bad I was ;)

Some notes about the provided code

The purpose of the given code is definitively not to provide you with a bullet proof code. If you want robust code, then use dynamically allocated memory instead of static buffers (or at least increase this 256 characters limitation…), always use strncpy() rather than basic strcpy to avoid buffer overflows, etc… But eh, I’m not here to teach how to write proper C !

Stay tuned!
What others think

«I've just discovered your gUSB winamp plugin which I think is brilliant. Does exactly what I need: lets me add music to my MP3 player straight from winamp.»

Douglas E.