id3v1tag -luokka

Alexer 07.01.05 21:45

mp3:issa käytettyjen id3v1 ja id3v1.1 tagien käsittelyyn tarkoitettu luokka + esimerkkiohjelma, joka demonstroi käyttöä

 Tekstiversio  Arvo: 0 (6 ääntä)  Äänestä: +  -
/*
        Olen kommentoinnin kannalta aivan 100% varmasti huonoin koodari,
        joka löytyy. Koitin tuohon luokan cpp-filuun saada edes funktioitten
        tarkoitukset jotenkin esille.
       
        ..Vanha koira ei opi uusia temppuja..
       
        Ensimmäinen koodini mureakuhaan, pistän lähinnä siksi, että huomasin
        suureksi hämmästyksekseni, että tämä on kohtuullisen selkeää koodia -
        kehtaa siis juuri ja juuri pistää näytteille - toisin kuin yleensä
        alunperin vain omaan käyttöön ajatellut luokkani. :)

        Toivottavasti tästä on jollekulle hyötyä.

Niin, ja, toimii siis ainakin linuxissa ja windowsissa.
*/




/*
        genretable.hpp
*/


#ifndef GENRETABLEHPP
#define GENRETABLEHPP

#include <stdlib.h>

/*
        Tämä taulukko on sitten suoraan mplayerin sorsista copypastella,
        ku ei jaksanu ite kirjottaa :E
*/


char *genreTable[] = {
        "Blues",                //0
        "Classic Rock",
        "Country",
        "Dance",
        "Disco",
        "Funk",
        "Grunge",
        "Hip-Hop",
        "Jazz",
        "Metal",
        "New Age",            //10
        "Oldies",
        "Other",
        "Pop",
        "R&B",
        "Rap",
        "Reggae",
        "Rock",
        "Techno",
        "Industrial",
        "Alternative",    //20
        "Ska",
        "Death Metal",
        "Pranks",
        "Soundtrack",
        "Eurotechno",
        "Ambient",
        "Trip-Hop",
        "Vocal",
        "Jazz+Funk",
        "Fusion",              //30
        "Trance",
        "Classical",
        "Instrumental",
        "Acid",
        "House",
        "Game",
        "Sound Clip",
        "Gospel",
        "Noise",
        "Alternative Rock",     //40
        "Bass",
        "Soul",
        "Punk",
        "Space",
        "Meditative",
        "Instrumental Pop",
        "Instrumental Rock",
        "Ethnic",
        "Gothic",
        "Darkwave",          //50
        "Techno-Industrial",
        "Electronic",
        "Pop-Folk",
        "Eurodance",
        "Dream",
        "Southern Rock",
        "Comedy",
        "Cult",
        "Gangsta",
        "Top 40",              //60
        "Christian Rap",
        "Pop/Funk",
        "Jungle",
        "Native American",
        "Cabaret",
        "New Wave",
        "Psychadelic",
        "Rave",
        "Show Tunes",
        "Trailer",            //70
        "Lo-Fi",
        "Tribal",
        "Acid Punk",
        "Acid Jazz",
        "Polka",
        "Retro",
        "Musical",
        "Rock & Roll",
        "Hard Rock",
        "Folk",   //80
        "Folk/Rock",
        "National Folk",
        "Swing",
        "Fast-Fusion",
        "Bebop",
        "Latin",
        "Revival",
        "Celtic",
        "Bluegrass",
        "Avantgarde",      //90
        "Gothic Rock",
        "Progressive Rock",
        "Psychedelic Rock",
        "Symphonic Rock",
        "Slow Rock",
        "Big Band",
        "Chorus",
        "Easy Listening",
        "Acoustic",
        "Humour",              //100
        "Speech",
        "Chanson",
        "Opera",
        "Chamber Music",
        "Sonata",
        "Symphony",
        "Booty Bass",
        "Primus",
        "Porn Groove",
        "Satire",              //110
        "Slow Jam",
        "Club",
        "Tango",
        "Samba",
        "Folklore",
        "Ballad",
        "Power Ballad",
        "Rhytmic Soul",
        "Freestyle",
        "Duet",   //120
        "Punk Rock",
        "Drum Solo",
        "Acapella",
        "Euro-House",
        "Dance Hall",
        "Goa",
        "Drum & Bass",
        "Club-House",
        "Hardcore",
        "Terror",              //130
        "Indie",
        "BritPop",
        "Negerpunk",
        "Polsk Punk",
        "Beat",
        "Christian Gangsta Rap",
        "Heavy Metal",
        "Black Metal",
        "Crossover",
        "Contemporary Christian",       //140
        "Christian Rock",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "J-Pop",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //150
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //160
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //170
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //180
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //190
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //200
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //210
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //220
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //230
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //240
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",            //250
        "Unknown",
        "Unknown",
        "Unknown",
        "Unknown",
        NULL
};

#else

extern char *genreTable[];

#endif






/*
        id3v1tag.hpp
*/


#ifndef ID3V1XTAGHPP
#define ID3V1XTAGHPP

class id3v1tag{


        public:
                enum id3v1tag_str_member{
                        song,
                        artist,
                        album,
                        year,
                        comment,
                        track_str,
                        genre_str,
                        none
                };
               
                enum id3v1tag_char_member{
                        track,
                        genre,
                };


        private:
                struct raw_id3v1tag{
                        char tag[3];
                        char song[30];
                        char artist[30];
                        char album[30];
                        char year[4];
                        char comment[28];
                        char zero;
                        char track;
                        char genre;
                };


        private:
                raw_id3v1tag vTag;
               
                char vSong[31];
                char vArtist[31];
                char vAlbum[31];
                char vYear[5];
                char vComment[31];
                char vTrack;
                char vGenre;


        private:
                bool has_tag();
                bool raw_has_track();
                bool parsed_has_track();
               
                void clear_raw();
               
                void raw_to_parsed();
                void parsed_to_raw();
                void raw_to_parsed(char *raw, char *parsed, int len);
               
                int raw_read(const char *pFname);
                int raw_write(const char *pFname);


        public:
                id3v1tag();
                id3v1tag(char *pFname);
                id3v1tag(id3v1tag &pTag);
                ~id3v1tag();
               
                int has(char *pFname);
               
                int read(const char *pFname);
                int write(const char *pFname);
                int remove(const char *pFname);
               
                void print();
               
                void clear();
               
                bool set(id3v1tag_str_member memb, char *pPar);
                bool set(id3v1tag_char_member memb, char pPar);
               
                const char *get(id3v1tag_str_member memb);
                char get(id3v1tag_char_member memb);
};

#endif





















/*
        id3v1tag.cpp
*/


#include <id3v1tag.hpp>  //tarvitsee näiden jälkeen käännössä optioksi "-I ."
#include <genretable.hpp>

#include <cstring>
#include <cstdio>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#define MIN(x,y) ((x<y)?(x):(y))

/*
        wintoosalle ei näköjään ollut ftruncatea
*/


#ifdef WIN32
#define ftruncate(FD,SIZE) _chsize(FD,SIZE)
#endif





/*Constructors*/

//Default constructor
id3v1tag::id3v1tag()
{
        clear_raw();
        clear();
}

//Constructor, fill fields from file
id3v1tag::id3v1tag(char *pFname)
{
        clear_raw();
        clear();
       
        read(pFname);
}

//Copy constructor, fill fields from another tag
id3v1tag::id3v1tag(id3v1tag &pTag)
{
        clear_raw();
        clear();
       
        memcpy(vSong,pTag.vSong,31);
        memcpy(vArtist,pTag.vArtist,31);
        memcpy(vAlbum,pTag.vAlbum,31);
        memcpy(vYear,pTag.vYear,31);
        memcpy(vComment,pTag.vComment,31);
       
        vTrack=pTag.vTrack;
        vGenre=pTag.vGenre;
}

//Default destructor
id3v1tag::~id3v1tag()
{
       
}





/*Raw statters*/

//Raw tag is really an id3v1 tag?
bool id3v1tag::has_tag(){
        if(strncmp(vTag.tag,"TAG",2)==0){
                return true;
        }
        return false;
}

//Raw tag is really an id3v1.1 tag?
bool id3v1tag::raw_has_track(){
        if(vTag.zero=='\0'&&vTag.track!='\0'){
                return true;
        }
        return false;
}

//Parsed needs to be written as an id3v1.1 tag?
bool id3v1tag::parsed_has_track(){
        if(vTrack!='\0'){
                return true;
        }
        return false;
}





/*Tag cleaners*/

//Take the trash out from raw tag
void id3v1tag::clear_raw(){
        memset(&vTag,0,128);
        vTag.genre=0xFF;//genre 0="Blues", 255=NULL
}

//Take the trash out of parsed tag
void id3v1tag::clear(){
        memset(vSong,0,31);
        memset(vArtist,0,31);
        memset(vAlbum,0,31);
        memset(vYear,0,5);
        memset(vComment,0,31);
        vTrack=0x00;
        vGenre=0xFF;
}






/*"Parsers"*/

//"Parse" tag to members
void id3v1tag::raw_to_parsed(){
        clear();
       
        raw_to_parsed(vTag.song,vSong,30);
        raw_to_parsed(vTag.artist,vArtist,30);
        raw_to_parsed(vTag.album,vAlbum,30);
        raw_to_parsed(vTag.year,vYear,4);
        raw_to_parsed(vTag.comment,vComment,30);
       
        vGenre=vTag.genre;
       
        if(raw_has_track()){//Track or no
                vTrack=vTag.track;
        }else{
                vTrack=0x00;
        }
}

//Write members to raw tag, for writing
void id3v1tag::parsed_to_raw(){
        clear_raw();
       
        memcpy(vTag.tag,"TAG",3);//"TAG" starts id3v1 tag
        memcpy(vTag.song,vSong,30);
        memcpy(vTag.artist,vArtist,30);
        memcpy(vTag.album,vAlbum,30);
        memcpy(vTag.year,vYear,4);
        memcpy(vTag.comment,vComment,30);
       
        vTag.genre=vGenre;
       
        //Write an id3v1.1 tag?
        if(parsed_has_track()){
                vTag.zero='\0';
                vTag.track=vTrack;
        }
}

//Parse one raw member to parsed member
void id3v1tag::raw_to_parsed(char *raw, char *parsed, int len){
        int i;
       
        for(i=0;i<len;i++){
                if(raw[i]=='\0'){
                        break;
                }
        }
       
        memcpy(parsed,raw,i);
        parsed[i+1]='\0';
}






/*File statters*/

//Has a file an id3v1 tag? -1=error, 0=no, 1=id3v1, 2=id3v1.1
int id3v1tag::has(char *pFname){
        int tmp=0;
       
        if(raw_read(pFname)==-1){
                return -1;
        }
       
        if(has_tag()){
                if(raw_has_track()){
                        tmp=2;
                }else{
                        tmp=1;
                }
        }
       
        clear_raw();
       
        return tmp;
}






/*File operations*/

//Read the last 128 bytes from the file
int id3v1tag::raw_read(const char *pFname){
        int fd=open(pFname,O_RDONLY);   
       
        if(fd==-1){
                return -1;
        }
       
        if(lseek(fd,0,SEEK_END)==sizeof(off_t)-1){
                close(fd);
                return -1;
        }
       
        if(lseek(fd,-128,SEEK_CUR)==sizeof(off_t)-1){
                close(fd);
                return -1;
        }

        if(::read(fd,vTag.tag,128)==-1){
                close(fd);
                return -1;
        }
       
        close(fd);
       
        return 0;
}

//Write the 128 bytes to the end of the file, if old tag exists, overwrite
int id3v1tag::raw_write(const char *pFname){
        int fd=open(pFname,O_RDWR);
        char buf[128];
       
        if(fd==-1){
                return -1;
        }
       
        if(lseek(fd,0,SEEK_END)==sizeof(off_t)-1){
                close(fd);
                return -1;
        }
               
        if(lseek(fd,-128,SEEK_CUR)==sizeof(off_t)-1){
                close(fd);
                return -1;
        }

        if(::read(fd,buf,128)==-1){
                close(fd);
                return -1;
        }

        if(!strncmp(buf,"TAG",3)){
                if(lseek(fd,-128,SEEK_CUR)==sizeof(off_t)-1){
                        close(fd);
                        return -1;
                }
        }
       
        if(::write(fd,vTag.tag,128)==-1){
                close(fd);
                return -1;
        }
       
        close(fd);
       
        return 0;
}





/*File alterators*/

//Read an id3v1 tag from file. -1=error, 1=id3v1, 2=id3v1.1
int id3v1tag::read(const char *pFname){
        int tmp=-1;
       
        if(raw_read(pFname)==-1){
                return -1;
        }
       
        if(has_tag()){
                raw_to_parsed();
                if(raw_has_track()){
                        tmp=2;
                }else{
                        tmp=1;
                }
        }
       
        clear_raw();
       
        return tmp;
}

//Write an id3v1 tag to file. -1=error, 1=wrote id3v1, 2=write id3v1.1
int id3v1tag::write(const char *pFname){
        int tmp;
       
        parsed_to_raw();
       
        if(raw_write(pFname)==-1){
                tmp=-1;
        }else{
                if(raw_has_track()){
                        tmp=2;
                }else{
                        tmp=1;
                }
        }
       
        clear_raw();
       
        return tmp;
}

//Remove an id3v1 tag from file. -1=error, 0=no tag was found, 1=id3v1 tag found and removed, 2=id3v1.1 tag found and removed
int id3v1tag::remove(const char *pFname){
        int fd=open(pFname,O_RDWR);
        int tmp;
       
        if(fd==-1){
                return -1;
        }
       
        if(lseek(fd,0,SEEK_END)==sizeof(off_t)-1){
                close(fd);
                return -1;
        }
               
        if(lseek(fd,-128,SEEK_CUR)==sizeof(off_t)-1){
                close(fd);
                return -1;
        }

        if(::read(fd,vTag.tag,128)==-1){
                close(fd);
                return -1;
        }

        if(!has_tag()){
                return 0;
        }
       
        if((tmp=lseek(fd,-128,SEEK_CUR))==sizeof(off_t)-1){
                close(fd);
                return -1;
        }
       
        if(ftruncate(fd,tmp)==-1){
                close(fd);
                return -1;
        }
       
        close(fd);
       
        return (raw_has_track())?(2):(1);
}



/*Printing*/

//Print the tag
void id3v1tag::print(){
        printf("Artist: %s\n",vArtist);
        printf("Album: %s\n",vAlbum);
        printf("Year: %s\n",vYear);
        printf("Genre: %s\n",genreTable[(unsigned char)vGenre]);
       
        if(parsed_has_track()){
                printf("Track: %d\n",vTrack);
        }
       
        printf("Song: %s\n",vSong);
        printf("Comment: %s\n",vComment);
}




/*Getters and setters*/

//Set the content of one of the fields
bool id3v1tag::set(id3v1tag_str_member memb, char *pPar){
        int tmp;
       
        switch(memb){
                case song:
                        memcpy(vSong,pPar,MIN(strlen(pPar),30));
                        vSong[30]='\0';
                        break;
                case artist:
                        memcpy(vArtist,pPar,MIN(strlen(pPar),30));
                        vArtist[30]='\0';
                        break;
                case album:
                        memcpy(vAlbum,pPar,MIN(strlen(pPar),30));
                        vAlbum[30]='\0';
                        break;
                case year:
                        memcpy(vYear,pPar,MIN(strlen(pPar),4));
                        vYear[4]='\0';
                        break;
                case comment:
                        memcpy(vComment,pPar,MIN(strlen(pPar),30));
                        vComment[30]='\0';
                        break;
                case track_str:
                        if(sscanf(pPar,"%d",&tmp)<1){
                                return false;
                        }
                        if(tmp>255||tmp<0){
                                return false;
                        }
                        vTrack=(unsigned char)tmp;
                        break;
                case genre_str:
                        if(sscanf(pPar,"%d",&tmp)<1){
                                return false;
                        }
                        if(tmp>255||tmp<0){
                                return false;
                        }
                        vGenre=(unsigned char)tmp;
                        break;
                default:
                        return false;
                        break;
        }
        return true;
}

//Set the content of the one-character-fields
bool id3v1tag::set(id3v1tag_char_member memb, char pPar){
        switch(memb){
                case track:
                        vTrack=pPar;
                        break;
                case genre:
                        vGenre=pPar;
                        break;
                default:
                        return false;
                        break;
        }
        return true;
}

//Get the content of one of the fields
const char *id3v1tag::get(id3v1tag_str_member memb){
        switch(memb){
                case song:
                        return vSong;
                        break;
                case artist:
                        return vArtist;
                        break;
                case album:
                        return vAlbum;
                        break;
                case year:
                        return vYear;
                        break;
                case comment:
                        return vComment;
                        break;
                case genre_str:
                        return genreTable[(unsigned char)vGenre];
                        break;
                default:
                        return NULL;
                        break;
        }
}

//get the one-character-fields
char id3v1tag::get(id3v1tag_char_member memb){
        switch(memb){
                case track:
                        return vTrack;
                        break;
                case genre:
                        return vGenre;
                        break;
                default:
                        return 0x00;
                        break;
        }
}

















/*
        id3v1tagger.cpp
       
        Eli tämä on luokan käyttöä demonstroiva ohjelma,
        kyhäsin nopeasti, joten en anna mitään takuita. ;)
        Ihan hyvin näytti kyllä toimivan..
       
        Tätä tiedostoa en sitten jaksanut alkaa kommentoimaan,
        mutta muu varmaan on suht selkeää, parametreja parsettavaa
        luuppia lukuunottamatta. Tuo luuppi ei kuitenkaan ole idean
        kannalta tärkeä, joten menköön.
       
        Ja kyllä, tuo luuppi näyttää jopa omasta mielestäni
        vähintäänkin mielenkiintoiselta........
*/


#include <id3v1tag.hpp>
#include <vector>
#include <string>
using namespace std;

struct mpair{
        char mode;
        id3v1tag::id3v1tag_str_member memb;
};

mpair mpairs[]={
        {'a',id3v1tag::artist},
        {'l',id3v1tag::album},
        {'s',id3v1tag::song},
        {'y',id3v1tag::year},
        {'c',id3v1tag::comment},
        {'t',id3v1tag::track_str},
        {'g',id3v1tag::genre_str},
        {'x',id3v1tag::none}
};

id3v1tag::id3v1tag_str_member get_memb(char x){
        int i;
        for(i=0;i<7;i++){
                if(mpairs[i].mode==x){
                        return mpairs[i].memb;
                }
        }
        return mpairs[i].memb;
}

void usage(char **argv){
        printf("Usage: %s mode [parametrs] file1 [file2 ...]\n",argv[0]);
        printf("\nModes:\n");
        printf("  r - Read tag from file\n");
        printf("  w - Write tag to file\n");
        printf("  d - Delete tag from file\n");
        printf("\nWrite parametres:\n");
        printf("  -a   Artist\n");
        printf("  -l   Album\n");
        printf("  -s   Song\n");
        printf("  -y   Year\n");
        printf("  -c   Comment\n");
        printf("  -g   Genre\n");
        printf("  -t   Track\n");
        printf("\nExamples:\n");
        printf("  %s r foo.mp3\n",argv[0]);
        printf("  %s d foo.mp3\n",argv[0]);
        printf("  %s w foo.mp3 -c\"This is a great comment\"\n",argv[0]);
        exit(EXIT_FAILURE);
}

int main(int argc, char **argv){
        id3v1tag tag;
        vector<string> files;
        vector<string>::iterator iter;
        int mode,i,a;
        char last='\0';
       
        if(argc<3){
                usage(argv);
        }
       
        switch(argv[1][0]){
                case 'r':
                        mode=0;
                        break;
                case 'w':
                        mode=1;
                        break;
                case 'd':
                        mode=2;
                        break;
                default:
                        usage(argv);
                        break;
        }
       
        for(i=2;i<argc;i++){
                if(argv[i][0]=='-'){
                        switch(last){
                                case '-':
                                        files.push_back(argv[i]);
                                        last='\0';
                                        continue;
                                case '\0':
                                        if(strlen(argv[i])<2){
                                                last='-';
                                        }
                                        switch(argv[i][1]){
                                                case '-':
                                                        if(strlen(argv[i])>2){
                                                                files.push_back(argv[i]+2);
                                                                last='\0';
                                                        }else{
                                                                last='-';
                                                        }
                                                        continue;
                                                default:
                                                        last=argv[i][1];
                                                       
                                                        if(strlen(argv[i])>2){
                                                                if(!tag.set(get_memb(last),argv[i]+2)){
                                                                        printf("Error: Weird parameter: -%c\n",last);
                                                                }
                                                                last='\0';
                                                        }
                                                        continue;
                                        }
                                default:
                                        if(!tag.set(get_memb(last),argv[i])){
                                                printf("Error: Weird parameter: -%c\n",last);
                                        }
                                        last='\0';
                                        break;
                        }
                }
               
                switch(last){
                        case '-':
                        case '\0':
                                files.push_back(argv[i]);
                                last='\0';
                                continue;
                        default:
                                if(!tag.set(get_memb(last),argv[i])){
                                        printf("Error: Weird parameter: -%c\n",last);
                                }
                                last='\0';
                                break;
                }
        }
       
        if(mode==0){
                printf("\nGenerated tag:\n");
                tag.print();
        }
       
        for(iter=files.begin();iter!=files.end();iter++){
                switch(mode){
                        case 0:
                                if(tag.read(iter->c_str())==-1){
                                        printf("\nError reading tag from \"%s\"\n",iter->c_str());
                                        break;
                                }
                                printf("\nFilename: %s\n",iter->c_str());
                                tag.print();
                                break;
                        case 1:
                                if(tag.write(iter->c_str())==-1){
                                        printf("\nError writing tag to \"%s\"\n",iter->c_str());
                                        break;
                                }
                                printf("\nWrote \"%s\"\n",iter->c_str());
                                break;
                        case 2:
                                if(tag.remove(iter->c_str())==-1){
                                        printf("\nError removing tag from \"%s\"\n",iter->c_str());
                                        break;
                                }
                                printf("\nTag removed from \"%s\"\n",iter->c_str());
                                break;
                        default:
                                break;
                }
        }
       
        return 0;
}
 

ane 21:53 7.1.05 
Kannattaa käyttää id3libiä jos etsii vakaata, turvallista ja toimivaa ID3-tagien muokkauskirjastoa C++:lle.. Tähän en koskisi. Kaiken lisäksi, mikä ihmeen järki on sekoittaa näin pahasti C:tä ja C++:aa keskenään?
Alexer 01:14 8.1.05 
C:n ja C++:n sekottamisen myönnän, en ole vielä "oppinut" kirjoittamaan C++:aa niin C++maisesti, kuin kuuluisi. (No kaikki varmaan ymmärsi)
id3lib on vakaa, turvallinen ja toimiva, totta sekin, tukee myös id3v2-tageja, ei voi kiistää, etteikö se olisi hyvä.
Löytyy kuitenkin yksi asia, se jonka takia kirjoitinkin tämän luokan. id3lib:n rajapinta. Ainakin minulle se on aivan liian vaikeasti sulatettava.
Lisäksi, en tuntunut löytävän tuosta id3lib:n rajapinnasta funktiota, jolla saisi tagin kokonaan poistettua, voi tosin johtua siitä, etten tosiaan
oikein saanut sitä sulatettua. Voipi olla, että olen ainoa, mutta väitän, että id3lib:n rajapinta on aika kryptinen, ainakin, jos ei halua kuluttaa
ihan järjettömiä aikoja tutustuen siihen..