/************************************************************* * mpgtx an mpeg toolbox * * by Laurent Alacoque * * (c) 2001 * * You may copy, modify and redistribute this * * source file under the terms of the GNU Public License * ************************************************************/ #include "id3command.hh" /** * Parses commandline and decide what happens. * * @param argc argc of calling parent programm * @param argv argv of calling parent programm * @param arc_offset index begin of interesting args * @return 0 - ok, 1 - error or return code of called subroutine [0;1] */ int ParseID3Command(int argc, char** argv, int argc_offset){ BTRACK; // starting from argc_offset format is : // -h or // set FORMAT file ... file ... // mov FORMAT2 file .... file ... // del file ... file ... int argcount= argc -argc_offset; if (argcount==0){ fprintf(stderr,"You must specify at least a mode\n"); return 1; } if (!strcmp(argv[argc_offset],"-h")){ // this is -h option printf("tagmp3 an ID3v1 tag editor\n"); printf("Usage : tagmp3 [-n] [-v] [set|move|del|list|show] [FORMAT] [file...]\n\n"); printf(" OPTIONS:\n"); printf(" -v prints version information\n"); printf(" set sets ID3v1 tag according to FORMAT\n"); printf(" move moves mp3 file according to the FORMAT\n"); printf(" del removes ID3v1 tag from file (if any)\n"); printf(" list lists known music genres along with their numeric values\n\n"); printf(" FORMAT:\n"); printf(" for set mode : \"%%Y? %%X:VALUE ...\" sets the X field of tag\n"); printf(" with VALUE and asks for the value of Y field\n"); printf(" for move mode : \"mymp3/%%X/%%Y/%%Z.mp3\" move file to\n"); printf(" ./mymp3/tag{X}/tag{Y}/tag{Z}.mp3\n\n"); printf(" valid fields are : %%A artist %%t title %%T track %%y year\n"); printf(" %%a album %%c comment %%g genre\n"); return 0; } if (!strcmp(argv[argc_offset],"-n")){ //show only id3_Show_Only=true; argc_offset++; } if (!strcmp(argv[argc_offset],"list")){ // list genres for (int i=0; i< MAX_ID3_GENRE; i+=4){ if (i\n"); printf("updates, bugs, patch, money : http://mpgtx.sourceforge.net\n"); return 0; } if (!strcmp(argv[argc_offset],"show")){ if (argcount<2){ fprintf(stderr,"show id3v1 tag of what file?\n"); return 1; } return ParseShow(argc,argv,argc_offset+1); } if (!strcmp(argv[argc_offset],"set")){ if (argcount<3){ fprintf(stderr,"Not enough arguments for set mode\n"); return 1; } return ParseSet(argc,argv,argc_offset+1); } if (!strcmp(argv[argc_offset],"move")){ if (argcount<3){ fprintf(stderr,"Not enough argument for mov mode\n"); return 1; } return ParseMov(argc,argv,argc_offset+1); } if (!strcmp(argv[argc_offset],"del")){ if (argcount<2){ fprintf(stderr,"remove id3v1 tag from what?\n"); return 1; } return ParseDel(argc,argv,argc_offset+1); } // not a valid id3 mode fprintf(stderr,"invalid mode : %s\n",argv[argc_offset]); return 1; } /** * Shows ID3v1 TAG of files. * * @param argc argc of calling parent programm * @param argv argv of calling parent programm * @param arc_offset index begin of interesting args * @return allways 0, no error if there are no ID3v1 TAGs in file */ int ParseShow(int argc, char** argv, int argc_offset){ BTRACK; OpenedFile* my; for (int i = argc_offset; i < argc; i++){ my = OpenFile(argv[i]); if ((!my->thefile)){ continue; } // okay we have a file here. if (!my->thetag){ fprintf(stdout, "%s doesn't have any ID3v1 tag (Note: ID3v2 not supported)\n", argv[i]); } else{ printf("%s\n", argv[i]); printf(" Artist : %s\n", my->thetag->artist); printf(" Title : %s\n", my->thetag->title); printf(" Album : %s\n", my->thetag->album); if (my->thetag->track>0) printf(" Track : %d\n", my->thetag->track); printf(" Year : %s\n", my->thetag->year); if (my->thetag->genre < MAX_ID3_GENRE) printf(" Genre : %s\n", genre[my->thetag->genre]); printf(" Comment: %s\n\n", my->thetag->comment); } // lets delete and close before next opened file if (my->thefile) fclose (my->thefile); if (my->thetag) delete my->thetag; delete my; } return 0; } /** * Sets ID3v1 TAG of files. * * @param argc argc of calling parent programm * @param argv argv of calling parent programm * @param arc_offset index begin of interesting args * @return allways 0, no error if there are no ID3v1 TAGs in file */ int ParseSet(int argc, char** argv, int argc_offset){ BTRACK; OpenedFile* my; int retval=1; for (int i = argc_offset + 1; i < argc; i++) { my = OpenFile(argv[i]); if ((!my->thefile) || (my->filetype != REAL_MP3)) { continue; } // okay we have a file here. // FIXME: This could not be reached, DEPRECATED? if (my->filetype != REAL_MP3) { fprintf(stderr, "Skipping file %s ", argv[i]); if (my->filetype== FAKE_MP3) fprintf(stderr,"(Wave file)\n"); else fprintf(stderr,"(not an mp3)\n"); } printf("Setting %s tag \n",argv[i]); fflush(stdout); if (!SetID3(argv[argc_offset],my)){ // something's wrong with the format fprintf(stderr,"ERROR while setting the format\n"); retval=1; break; } else{ if(!id3_Show_Only) { // printf("Setting %s tag\n",argv[i]); WriteID3(my); retval=0; } else{ // printf("\n"); } } // lets delete and close before next opened file if (my->thefile) fclose (my->thefile); if (my->thetag) delete my->thetag; delete my; } return retval; } /** * Parses command line to prepare moving of MP3 files. * * @param argc argc of calling parent programm * @param argv argv of calling parent programm * @param arc_offset index begin of interesting args * @return allways 0, no error if there are no ID3v1 TAGs in file */ int ParseMov(int argc, char** argv, int argc_offset){ BTRACK OpenedFile* my; int i; int j; int last_slash; char* tempfilename=new char[300]; char mybasename[300]; char myfilename[300]; // for each remaining arg for ( i=argc_offset+1; ithefile){ delete my; continue; } // opened, with a tag // compute basename and filename last_slash=-1; if (!strstr(argv[i],"/")){ // no base directory in file name mybasename[0]=0; } else{ for(j =0; j < int(strlen(argv[i])); j++){ if (argv[i][j]=='/') last_slash=j; } for (j=0; j <= last_slash; j++){ mybasename[j]=argv[i][j]; } mybasename[j]=0; } // now we have the basename, last_slash is either // -1 (no basename) or the offset of the last slash last_slash++; for (j=last_slash; j<=int(strlen(argv[i])); j++){ myfilename[j-last_slash]=argv[i][j]; } //basename and filename are okay // if this is a fake move it in fake-mp3 // fake is a wav with mp3 extension if (my->filetype==FAKE_MP3){ sprintf(tempfilename,"fake-mp3/"); strcat(tempfilename,myfilename); Move2(argv[i],mybasename,tempfilename); if (my->thetag) delete my->thetag; delete my; continue; } // if this file doesn't have a tag // skip it if (! my->thetag){ fprintf(stderr,"Skipping file %s (no tag)\n",argv[i]); fclose (my->thefile); delete my; continue; } // if this is not a mp3 if (my->filetype==NOT_MP3){ fprintf(stderr,"Skipping %s(not an mpeg file)\n",argv[i]); } else { // Now we have to handle the format string. tempfilename[0] = 0; int name_offset=0; int fieldsize=-1; char *source=0; char* format= argv[argc_offset]; bool error_found=false; char temptracknum[10]; int track; int t,h,k,m; char* fieldname=0; unsigned char genre_num; const char* genre_as_string; int genre_length; j=0; // the format string is argv[argc_offset] while ((j < int(strlen(format)))&& !error_found ){ if (format[j] == '%'){ if (format[j+1] != '%'){ // it starts like a field name fieldsize=-1; switch (format[j+1]){ case 'a': fieldname="album"; source= my->thetag->album; fieldsize=30; break; case 'A': fieldname="artist"; source= my->thetag->artist; fieldsize=30; break; case 't': fieldname="title"; source= my->thetag->title; fieldsize=30; break; case 'y': fieldname="year"; source= my->thetag->year; fieldsize=4; break; case 'c': fieldname="comment"; source= my->thetag->comment; fieldsize=30; break; case 'T': track= my->thetag->track; if (track <0){ fprintf(stderr,"%s: empty track value needed by format\n",argv[i]); error_found=true; } else { sprintf(temptracknum,"%02d",track); for (h=0; h < int(strlen(temptracknum));h++){ tempfilename[name_offset++]=temptracknum[h]; } } break; case 'g': genre_num = my->thetag->genre; if (genre_num >= MAX_ID3_GENRE){ genre_as_string = "Unknown"; genre_length=7; } else { genre_as_string=genre[genre_num]; genre_length=strlen(genre_as_string); } for (t=0; t 0){ //eat leading white spaces for (k=0;(k = fieldsize) || (source[k] == 0)){ // this was a all white field... skip ? fprintf(stderr,"%s: empty field %s needed by format\n",argv[i],fieldname); error_found=true; } else { // k points to the first valid char int last_valid=k; for (int l =0; (lthefile) fclose (my->thefile); if (my->thetag) delete my->thetag; delete my; } delete[] tempfilename; return 0; } /** * Removes ID3v1 TAG from files. * * @param argc argc of calling parent programm * @param argv argv of calling parent programm * @param arc_offset index begin of interesting args * @return 1 - error: windows version dos not work yet, otherwise allways 0 */ int ParseDel(int argc, char** argv, int argc_offset){ BTRACK #ifdef _WIN32 // windows version /* * from: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/base/setendoffile.asp * * The SetEndOfFile function moves the end-of-file (EOF) position for the specified file * to the current position of the file pointer. * * This function sets the physical end of a file (as indicated by allocated clusters). * To set the logical end of a file, use the SetFileValidData function. * * BOOL SetEndOfFile( * HANDLE hFile * ); */ printf("del\n"); fprintf(stderr,"del mode is not implemented under windows : \nstill don't know how to truncate a file"); return 1; #else off_t FileSize; OpenedFile* my; for (int i=argc_offset; i < argc; i++){ printf("Deleting tag in %s\n",argv[i]); my=OpenFile(argv[i]); if (!my->thefile){ printf("Can not open file\n"); if (my->thetag) delete my->thetag; delete my; continue; } // okay we have a file here. if (my->thetag) { // printf("tag is present\n"); // this file has a tag FSEEK(my->thefile,0,SEEK_END); // printf("Size before : %ld\n",ftello(my->thefile)); FileSize=FTELL(my->thefile)-128; // printf("Size after : %ld\n",FileSize); if (!id3_Show_Only) if(truncate(argv[i],FileSize)){ fprintf(stderr,"Unable to truncate file \n"); perror(argv[i]); } } //printf("File doesn't have any tag\n"); // lets delete and close before next opened file if (my->thefile) fclose (my->thefile); if (my->thetag) delete my->thetag; delete my; } return 0; #endif // _WIN32 } /** * Fills a OpenedFile structure with TAG infos of a file according given FileName * * @param FileName Name of file to open * @return Pointer to filled structure */ OpenedFile* OpenFile(char* FileName){ BTRACK; unsigned char start[4]; char TAG[128]; OpenedFile* my = new OpenedFile; my->thetag=0; my->thefile=0; my->filetype=NOT_MP3; my->thefile=fopen(FileName,"r+b"); if (!my->thefile){ // error perror(FileName); return my; } fread(start,4,1,my->thefile); if (!strncmp((char*)(start),"RIFF",4)){ if (!strstr(FileName,".mp")||!strstr(FileName,".MP")){ // fprintf(stderr,"Skipping Fake mp3 file %s (Wave file)\n",FileName); my->filetype=FAKE_MP3; return my; } else { // fprintf(stderr,"Skipping Wave file %s\n",FileName); my->filetype=NOT_MP3; return my; } } if ((start[0]==0xFF) && ((start[1] & 0xE0) == 0xE0)){ my->filetype=REAL_MP3; } else if (start[0]== 'I' && start[1]=='D' && start[2]=='3'){ my->filetype=REAL_MP3; } else{ //doesn't start like an mp3 if (!strstr(FileName,".mp")||!strstr(FileName,".MP")){ my->filetype=REAL_MP3; } else { fprintf(stderr,"Skipping unknown file %s\n",FileName); my->filetype=NOT_MP3; return my; } } FSEEK(my->thefile,-128,SEEK_END); fread(TAG,128,1,my->thefile); if ( strncmp (TAG,"TAG",3) ){ // doesn't begin with TAG : no tag return my; } // begins with TAG : parse it. my->thetag=new myID3tag; strncpy(my->thetag->title,&TAG[3],30); strncpy(my->thetag->artist,&TAG[33],30); strncpy(my->thetag->album,&TAG[63],30); strncpy(my->thetag->year,&TAG[93],4); strncpy(my->thetag->comment,&TAG[97],30); if (TAG[125]==0){ my->thetag->track=TAG[126]; } else { my->thetag->track=-1; } my->thetag->genre=(unsigned char) TAG[127]; return my; } /** * Sets ID3v1 TAG of a MP3 file according given format string. * * @param format Format string with TAGs to set * @param tag File to tag. * @return false on format errors, true otherwise */ bool SetID3(char* format,OpenedFile* tag){ BTRACK; // change tag->thetag to reflect format if (! tag->thetag) { tag->thetag=new myID3tag; tag->thetag->album[0]=0; tag->thetag->artist[0]=0; tag->thetag->year[0]=0; tag->thetag->comment[0]=0; tag->thetag->title[0]=0; tag->thetag->track=-1; tag->thetag->genre=255; } char *target=0; // parse the format int fieldsize; int tracknum=-1; int genre=-1; int length = strlen(format); for(int i=0; i <= length-3;){ // first char must be % // second char must be tAaycgT // third char must be : or ? while(format[i]==' ') i++; if (format[i]!='%' || (format[i+2] != ':' && format [i+2] != '?')){ fprintf(stderr,"Invalid format \"%s\"\n",format+i); return false; } fieldsize=-1; switch (format[i+1]){ case 'a': if (format[i+2]=='?') fprintf(stdout,"Album : "); target= tag->thetag->album; fieldsize=30; break; case 'A': if (format[i+2]=='?') fprintf(stdout,"Artist : "); target= tag->thetag->artist; fieldsize=30; break; case 't': if (format[i+2]=='?') fprintf(stdout,"Title : "); target= tag->thetag->title; fieldsize=30; break; case 'c': if (format[i+2]=='?') fprintf(stdout,"Comment: "); target= tag->thetag->comment; fieldsize=30; break; case 'y': if (format[i+2]=='?') fprintf(stdout,"Year : "); target= tag->thetag->year; fieldsize=4; break; case 'g': if (format[i+2]=='?') fprintf(stdout,"Genre : "); break; case 'T': if (format[i+2]=='?') fprintf(stdout,"Track : "); break; } // treat the '?' if (format[i+2]=='?'){ // ask user if (fieldsize>0){ //any char* fgets(target,fieldsize+1,stdin); // now remove the trailing \n if any if (strlen(target)!=0){ if (target[strlen(target)-1]=='\n') target[strlen(target)-1]=0; else { // mmmmm no \n at the end : flush input while (getchar()!='\n'); } } } else{ // a number for genre or track if (format[i+1]=='T') scanf("%d",&tracknum); if (format[i+1]=='g') scanf("%d",&genre); // printf("read T %d g %d\n",tracknum,genre); } i+=3; continue; } // treat the ':' // is this a 'standard' field? if (fieldsize==-1){ if (length-i >3){ if (format[i+1]=='T') sscanf(&format[i+3],"%d",&tracknum); if (format[i+1]=='g') sscanf(&format[i+3],"%d",&genre); // printf("scanned T %d g %d\n",tracknum,genre); i+=3; } while ((format[i]>='0' && format[i]<='9')||(format[i]==' ')) i++; continue; } // this is a char * field // move i to the start of field i+=3; // now copy everything until a % or fieldsize or format length bool finished=false; int n_processed=0; while (!finished){ if ( // a % not followed by a % (format[i]=='%' && format[i+1]!='%')|| // end of format (format[i]=='\0') ) { finished=true; // while (n_processed thetag->genre=255; // unspecified genre //} if (genre>=0) tag->thetag->genre=genre; if (tracknum >=0 && tracknum <256 ){ tag->thetag->track=(unsigned char) tracknum; } return true; } /** * Write ID3v1 TAG to given file. * * @param filename MP3 file to set ID3v1 TAG. */ void WriteID3(OpenedFile* filename){ BTRACK //write tag->thetag in tag->thefile /* printf ("artist %s\ntitle %s \nalbum %s\nyear %s\ncomment %s\ngenre %d\ntrack%d\n\n", filename->thetag->artist , filename->thetag->title , filename->thetag->album, filename->thetag->year , filename->thetag->comment , filename->thetag->genre , filename->thetag->track); */ if (filename->thefile==0){ fprintf(stderr,"ERROR : no valid FILE* in WriteID3\n"); return; } if (filename->thetag==0){ fprintf(stderr,"ERROR : no valid tag in WriteID3\n"); return; } char TAG[128]; TAG[0]='T'; TAG[1]='A'; TAG[2]='G'; int length,i; length=strlen(filename->thetag->title); for (i=0; i < length; i++) TAG[3+i]=filename->thetag->title[i]; for (;i<30; i++) TAG[3+i]=' '; length=strlen(filename->thetag->artist); for (i=0; i < length; i++) TAG[33+i]=filename->thetag->artist[i]; for (;i<30; i++) TAG[33+i]=' '; length=strlen(filename->thetag->album); for (i=0; i < length; i++) TAG[63+i]=filename->thetag->album[i]; for (;i<30; i++) TAG[63+i]=' '; length=strlen(filename->thetag->year); for (i=0; i < length; i++) TAG[93+i]=filename->thetag->year[i]; for (;i<4; i++) TAG[93+i]=' '; length=strlen(filename->thetag->comment); for (i=0; i < length; i++) TAG[97+i]=filename->thetag->comment[i]; for (;i<30; i++) TAG[97+i]=' '; if (filename->thetag->track >0){ TAG[125]=0; TAG[126]=(unsigned char) filename->thetag->track; } //if(filename->thetag->genre !=255) TAG[127]=(unsigned char) filename->thetag->genre; TAG[127]= filename->thetag->genre; // okay we have our chunk. char checkTAG[3]; FSEEK(filename->thefile, -128, SEEK_END); fread(checkTAG,3,1,filename->thefile); if (!strncmp(checkTAG,"TAG",3)){ // a TAG is present FSEEK(filename->thefile, -128, SEEK_END); fwrite(TAG,128,1,filename->thefile); }else{ // no tag in this file FSEEK(filename->thefile,0,SEEK_END); fwrite(TAG,128,1,filename->thefile); } } /** * Asks user for creation of a new directory. * *@param path Path of new directory *@return true if user allows creation, false if deny */ bool AskDirCreation(char* path){ BTRACK fprintf(stderr,"Create directory %s ?:[N/y]",path); char answer=getchar(); while(getchar()!='\n'); RTRACK if (answer=='y' || answer=='Y') return true; return false; } /** * Moves file to destination, creates intermediate directories if necessary. * * @param name Filename * @param basedir Source directory * @param dest Destination directory */ void Move2(const char* name,const char* basedir,const char* dest){ BTRACK struct stat mystats; int ret; char *tempdest; char *fullname; int move2dirsize=0; fullname = new char [strlen(name)+strlen(basedir)+1]; strcpy(fullname,basedir); strcat(fullname,name); // if the dest ends with a / , keep space for the name if (dest[strlen(dest)-1]=='/'){ move2dirsize=strlen (name); } //handle the full path (begins with a /) if (dest[0]=='/'){ tempdest=new char[strlen(dest)+1+move2dirsize]; strcpy(tempdest,dest); } //handle the ~/ (home dir) else if(dest[0]=='~' && dest[1]=='/'){ struct passwd* mypassentry; mypassentry=getpwuid(getuid()); if (!mypassentry || !(mypassentry->pw_dir)){ fprintf(stderr,"Could not resolve your home directory, use absolute path\n"); return; } tempdest= new char [strlen(mypassentry->pw_dir)+strlen(dest)+1+move2dirsize]; if (mypassentry->pw_dir[strlen(mypassentry->pw_dir)-1] == '/'){ // pwdir ends with a slash strcpy(tempdest,mypassentry->pw_dir); strcat(tempdest,&dest[2]); // dest[2] after the / } else { //pw_dir doesn't end with a slash strcpy(tempdest,mypassentry->pw_dir); strcat(tempdest,&dest[1]); } } //handle the ./ else if (dest[0]=='.' && dest[1]=='/'){ //explicitely relative path tempdest= new char[strlen(dest)+strlen(basedir)+1+move2dirsize]; strcpy(tempdest,basedir); strcat(tempdest,&dest[2]); //after the slash } // dest is relative, don't change it else { tempdest= new char[strlen(dest)+strlen(basedir)+1+move2dirsize]; strcpy(tempdest,basedir); strcat(tempdest,dest); } if (move2dirsize){ //the format ends with a /, append name strcat(tempdest,name); } //printf("%s\n =>%s\n\n",name,tempdest); //this for is to create intermediate directories for (int i= 0; i < int(strlen(tempdest)); i++){ if (tempdest[i]=='/' && i!=0){ //check if dir exists //hide the trailing path tempdest[i]=0; ret=stat(tempdest,&mystats); if (!ret){ //stat worked //check if this is a directory if ((mystats.st_mode & S_IFMT)==S_IFREG){ fprintf(stderr,"Error : %s is not a directory\n",tempdest); delete [] tempdest; return; } } else{ //didn't work... check the error // printf("%s ",tempdest); switch(errno){ case ENOENT: // printf("ENOENT\n"); if (id3_Show_Only) { printf("Would Create directory %s (if not created before)\n",tempdest); } else{ if(AskDirCreation(tempdest)) mkdir(tempdest,0777); else return; } break; case ENOTDIR: // a part of the path is a directory fprintf(stderr,"Error : %s is not a directory\n",tempdest); delete[]tempdest; return; break; case EACCES: perror(tempdest); delete[] tempdest; return; break; default : perror(tempdest); delete[] tempdest; return; break; } } //show the trailing path again tempdest[i]='/'; } } // okay we can rename // check if the destination already exist ret = stat(tempdest,&mystats); if (!ret){ printf("Skipping %s (file exists)\n",tempdest); delete[] tempdest; return; } if(id3_Show_Only){ printf("Would move %s\n",fullname); printf("to %s\n\n",tempdest); } else{ ret=rename(fullname,tempdest); if (ret){ perror(fullname); } else{ //printf("%s=>%s\n",name,tempdest); } } }