1 #if defined HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4 
5 #include <cstdio>
6 #include <iostream>
7 #include <cstring>
8 #include <id3/tag.h>
9 #include <getopt.h>
10 #include <id3/misc_support.h>
11 #ifdef WIN32
12 #include <io.h>
13 #define snprintf _snprintf
14 #endif
15 
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 
19 #include "genre.h"
20 
21 #define MAXNOFRAMES 1000
22 
23 #define TMPSIZE 255
24 
25 /* Write both tags by default */
26 flags_t UpdFlags = ID3TT_ALL;
27 
28 
PrintUsage(char * sName)29 void PrintUsage(char *sName)
30 {
31   std::cout << "Usage: " << sName << " [OPTION]... [FILE]..." << std::endl;
32   std::cout << "Adds/Modifies/Removes/Views id3v2 tags, modifies/converts/lists id3v1 tags" << std::endl;
33   std::cout << std::endl;
34   std::cout << "  -h,  --help               Display this help and exit" << std::endl;
35   std::cout << "  -f,  --list-frames        Display all possible frames for id3v2" << std::endl;
36   std::cout << "  -L,  --list-genres        Lists all id3v1 genres" << std::endl;
37   std::cout << "  -v,  --version            Display version information and exit" << std::endl;
38   std::cout << "  -l,  --list               Lists the tag(s) on the file(s)" << std::endl;
39   std::cout << "  -d,  --delete-v2          Deletes id3v2 tags" << std::endl;
40   std::cout << "  -s,  --delete-v1          Deletes id3v1 tags" << std::endl;
41   std::cout << "  -D,  --delete-all         Deletes both id3v1 and id3v2 tags" << std::endl;
42   std::cout << "  -C,  --convert            Converts id3v1 tag to id3v2" << std::endl;
43   std::cout << "  -1,  --id3v1-only         Writes only id3v1 tag" << std::endl;
44   std::cout << "  -2,  --id3v2-only         Writes only id3v2 tag" << std::endl;
45   std::cout << "  -r,  --remove-frame \"FRAMEID\"   Removes the specified id3v2 frame" << std::endl;
46   std::cout << "  -a,  --artist       \"ARTIST\"    Set the artist information" << std::endl;
47   std::cout << "  -A,  --album        \"ALBUM\"     Set the album title information" << std::endl;
48   std::cout << "  -t,  --song         \"SONG\"      Set the song title information" << std::endl;
49   std::cout << "  -c,  --comment      \"DESCRIPTION\":\"COMMENT\":\"LANGUAGE\"  "<< std::endl
50        << "                            Set the comment information (both" << std::endl
51        << "                            description and language optional)" << std::endl;
52   std::cout << "  -g,  --genre   num        Set the genre number" << std::endl;
53   std::cout << "  -y,  --year    num        Set the year" << std::endl;
54   std::cout << "  -T,  --track   num/num    Set the track number/(optional) total tracks" << std::endl;
55   std::cout << std::endl;
56   std::cout << "You can set the value for any id3v2 frame by using '--' and then frame id" << std::endl;
57   std::cout << "For example: " << std::endl;
58   std::cout << "        id3v2 --TIT3 \"Monkey!\" file.mp3" << std::endl;
59   std::cout << "would set the \"Subtitle/Description\" frame to \"Monkey!\". " << std::endl;
60   std::cout << std::endl;
61 }
62 
63 
PrintVersion(char * sName)64 void PrintVersion(char *sName)
65 {
66   std::cout << sName << " " << VERSION << std::endl;
67   std::cout << "Uses " << ID3LIB_FULL_NAME << std::endl << std::endl;
68 
69   std::cout << "This program adds/modifies/removes/views id3v2 tags, " << std::endl
70        << "and can convert from id3v1 tags" << std::endl;
71 }
72 
73 
74 extern void ListTag(int argc, char *argv[], int optind);
75 extern void PrintFrameHelp(char *sName);
76 extern void PrintGenreList();
77 
78 extern void DeleteTag(int argc, char *argv[], int optind, int whichTags);
79 extern void ConvertTag(int argc, char *argv[], int optind);
80 extern void DeleteFrame(int argc, char *argv[], int optind, char * frame);
81 
82 #ifdef SORT_RUNTIME
83 extern void InitGenres();
84 #endif  // SORT_RUNTIME
85 
main(int argc,char * argv[])86 int main( int argc, char *argv[])
87 {
88   int iOpt;
89   int argCounter = 0;
90   int ii;
91   char tmp[TMPSIZE];
92   FILE * fp;
93 
94   struct frameInfo {
95     enum ID3_FrameID id;
96     char *data;
97   } frameList[MAXNOFRAMES];
98 
99   int frameCounter = 0;
100 
101   while (true)
102   {
103     int option_index = 0;
104     int iLongOpt = 0;
105     int optFrameID = ID3FID_NOFRAME;
106     static struct option long_options[] =
107     {
108     // help and info
109       { "help",    no_argument,       &iLongOpt, 'h' },
110       { "list-frames",
111                    no_argument,       &iLongOpt, 'f' },
112       { "list-genres",
113                   no_argument,        &iLongOpt, 'L' },
114       { "version", no_argument,       &iLongOpt, 'v' },
115 
116     // list / remove / convert
117       { "list",   no_argument,        &iLongOpt, 'l' },
118       { "delete-v2",  no_argument,    &iLongOpt, 'd' },
119       { "delete-v1",
120                    no_argument,       &iLongOpt, 's' },
121       { "delete-all",
122                    no_argument,       &iLongOpt, 'D' },
123       { "convert", no_argument,       &iLongOpt, 'C' },
124       { "id3v1-only", no_argument,       &iLongOpt, '1' },
125       { "id3v2-only", no_argument,       &iLongOpt, '2' },
126       { "remove-frame", required_argument,  &iLongOpt, 'r' },
127 
128     // infomation to tag
129       { "artist",  required_argument, &iLongOpt, 'a' },
130       { "album",   required_argument, &iLongOpt, 'A' },
131       { "song",    required_argument, &iLongOpt, 't' },
132       { "comment", required_argument, &iLongOpt, 'c' },
133       { "genre",   required_argument, &iLongOpt, 'g' },
134       { "year",    required_argument, &iLongOpt, 'y' },
135       { "track",   required_argument, &iLongOpt, 'T' },
136       { "AENC",    required_argument, &optFrameID, ID3FID_AUDIOCRYPTO },
137       { "APIC",    required_argument, &optFrameID, ID3FID_PICTURE },
138       { "COMM",    required_argument, &optFrameID, ID3FID_COMMENT },
139     /* COMR too complex */
140       { "ENCR",    required_argument, &optFrameID, ID3FID_CRYPTOREG },
141       { "EQUA",    required_argument, &optFrameID, ID3FID_EQUALIZATION },
142       { "ETCO",    required_argument, &optFrameID, ID3FID_EVENTTIMING },
143       { "GEOB",    required_argument, &optFrameID, ID3FID_GENERALOBJECT },
144       { "GRID",    required_argument, &optFrameID, ID3FID_GROUPINGREG },
145       { "IPLS",    required_argument, &optFrameID, ID3FID_INVOLVEDPEOPLE },
146       { "LINK",    required_argument, &optFrameID, ID3FID_LINKEDINFO },
147       { "MCDI",    required_argument, &optFrameID, ID3FID_CDID },
148       { "MLLT",    required_argument, &optFrameID, ID3FID_MPEGLOOKUP },
149       { "OWNE",    required_argument, &optFrameID, ID3FID_OWNERSHIP },
150       { "PRIV",    required_argument, &optFrameID, ID3FID_PRIVATE },
151       { "PCNT",    required_argument, &optFrameID, ID3FID_PLAYCOUNTER },
152       { "POPM",    required_argument, &optFrameID, ID3FID_POPULARIMETER },
153       { "POSS",    required_argument, &optFrameID, ID3FID_POSITIONSYNC },
154       { "RBUF",    required_argument, &optFrameID, ID3FID_BUFFERSIZE },
155       { "RVAD",    required_argument, &optFrameID, ID3FID_VOLUMEADJ },
156       { "RVRB",    required_argument, &optFrameID, ID3FID_REVERB },
157       { "SYLT",    required_argument, &optFrameID, ID3FID_SYNCEDLYRICS },
158       { "SYTC",    required_argument, &optFrameID, ID3FID_SYNCEDTEMPO },
159       { "TALB",    required_argument, &optFrameID, ID3FID_ALBUM },
160       { "TBPM",    required_argument, &optFrameID, ID3FID_BPM },
161       { "TCOM",    required_argument, &optFrameID, ID3FID_COMPOSER },
162       { "TCON",    required_argument, &optFrameID, ID3FID_CONTENTTYPE },
163       { "TCOP",    required_argument, &optFrameID, ID3FID_COPYRIGHT },
164       { "TDAT",    required_argument, &optFrameID, ID3FID_DATE },
165       { "TDLY",    required_argument, &optFrameID, ID3FID_PLAYLISTDELAY },
166       { "TENC",    required_argument, &optFrameID, ID3FID_ENCODEDBY },
167       { "TEXT",    required_argument, &optFrameID, ID3FID_LYRICIST },
168       { "TFLT",    required_argument, &optFrameID, ID3FID_FILETYPE },
169       { "TIME",    required_argument, &optFrameID, ID3FID_TIME },
170       { "TIT1",    required_argument, &optFrameID, ID3FID_CONTENTGROUP },
171       { "TIT2",    required_argument, &optFrameID, ID3FID_TITLE },
172       { "TIT3",    required_argument, &optFrameID, ID3FID_SUBTITLE },
173       { "TKEY",    required_argument, &optFrameID, ID3FID_INITIALKEY },
174       { "TLAN",    required_argument, &optFrameID, ID3FID_LANGUAGE },
175       { "TLEN",    required_argument, &optFrameID, ID3FID_SONGLEN },
176       { "TMED",    required_argument, &optFrameID, ID3FID_MEDIATYPE },
177       { "TOAL",    required_argument, &optFrameID, ID3FID_ORIGALBUM },
178       { "TOFN",    required_argument, &optFrameID, ID3FID_ORIGFILENAME },
179       { "TOLY",    required_argument, &optFrameID, ID3FID_ORIGLYRICIST },
180       { "TOPE",    required_argument, &optFrameID, ID3FID_ORIGARTIST },
181       { "TORY",    required_argument, &optFrameID, ID3FID_ORIGYEAR },
182       { "TOWN",    required_argument, &optFrameID, ID3FID_FILEOWNER },
183       { "TPE1",    required_argument, &optFrameID, ID3FID_LEADARTIST },
184       { "TPE2",    required_argument, &optFrameID, ID3FID_BAND },
185       { "TPE3",    required_argument, &optFrameID, ID3FID_CONDUCTOR },
186       { "TPE4",    required_argument, &optFrameID, ID3FID_MIXARTIST },
187       { "TPOS",    required_argument, &optFrameID, ID3FID_PARTINSET },
188       { "TPUB",    required_argument, &optFrameID, ID3FID_PUBLISHER },
189       { "TRCK",    required_argument, &optFrameID, ID3FID_TRACKNUM },
190       { "TRDA",    required_argument, &optFrameID, ID3FID_RECORDINGDATES },
191       { "TRSN",    required_argument, &optFrameID, ID3FID_NETRADIOSTATION },
192       { "TRSO",    required_argument, &optFrameID, ID3FID_NETRADIOOWNER },
193       { "TSIZ",    required_argument, &optFrameID, ID3FID_SIZE },
194       { "TSRC",    required_argument, &optFrameID, ID3FID_ISRC },
195       { "TSSE",    required_argument, &optFrameID, ID3FID_ENCODERSETTINGS },
196       { "TXXX",    required_argument, &optFrameID, ID3FID_USERTEXT },
197       { "TYER",    required_argument, &optFrameID, ID3FID_YEAR },
198       { "UFID",    required_argument, &optFrameID, ID3FID_UNIQUEFILEID },
199       { "USER",    required_argument, &optFrameID, ID3FID_TERMSOFUSE },
200       { "USLT",    required_argument, &optFrameID, ID3FID_UNSYNCEDLYRICS },
201       { "WCOM",    required_argument, &optFrameID, ID3FID_WWWCOMMERCIALINFO },
202       { "WCOP",    required_argument, &optFrameID, ID3FID_WWWCOPYRIGHT },
203       { "WOAF",    required_argument, &optFrameID, ID3FID_WWWAUDIOFILE },
204       { "WOAR",    required_argument, &optFrameID, ID3FID_WWWARTIST },
205       { "WOAS",    required_argument, &optFrameID, ID3FID_WWWAUDIOSOURCE },
206       { "WORS",    required_argument, &optFrameID, ID3FID_WWWRADIOPAGE },
207       { "WPAY",    required_argument, &optFrameID, ID3FID_WWWPAYMENT },
208       { "WPUB",    required_argument, &optFrameID, ID3FID_WWWPUBLISHER },
209       { "WXXX",    required_argument, &optFrameID, ID3FID_WWWUSER },
210       { 0, 0, 0, 0 }
211     };
212     iOpt = getopt_long (argc, argv, "12hfLvldsDCr:a:A:t:c:g:y:T:",
213                         long_options, &option_index);
214 
215     if (iOpt == -1  && argCounter == 0)
216     {
217       PrintUsage(argv[0]);
218       exit(0);
219     }
220     else if (iOpt == -1)
221       break;
222     argCounter++;
223 
224     if (iOpt == 0) iOpt = iLongOpt;
225 //    if (iOpt == 0) iOpt = optFrameID;
226 
227 #ifdef SORT_RUNTIME
228     InitGenres();
229 #endif  // SORT_RUNTIME
230 
231     switch (iOpt)
232     {
233       case 0:
234                 frameList[frameCounter].id   = (enum ID3_FrameID)optFrameID;
235                 frameList[frameCounter].data = optarg;
236                 frameCounter++;
237                 break;
238       case '?':
239       case 'h': PrintUsage(argv[0]);    exit (0);
240       case 'r': DeleteFrame(argc, argv, optind, optarg);    exit (0);
241       case 'f': PrintFrameHelp(argv[0]);exit (0);
242       case 'L': PrintGenreList();       exit (0);
243       case 'v': PrintVersion(argv[0]);  exit (0);
244 
245     // listing / remove / convert -- see list.cpp and convert.cpp
246       case 'l': ListTag(argc, argv, optind);
247                                         exit (0);
248       case 'd': DeleteTag(argc, argv, optind, 2);
249                                         exit (0);
250       case 's': DeleteTag(argc, argv, optind, 1);
251                                         exit (0);
252       case 'D': DeleteTag(argc, argv, optind, 0);
253                                         exit (0);
254       case 'C': ConvertTag(argc, argv, optind);
255                                         exit (0);
256       case '1':
257 		UpdFlags = ID3TT_ID3V1;
258 		break;
259       case '2':
260 		UpdFlags = ID3TT_ID3V2;
261 		break;
262     // Tagging stuff
263       case 'a':
264                 frameList[frameCounter].id   = ID3FID_LEADARTIST;
265                 frameList[frameCounter].data = optarg;
266                 frameCounter++;
267                 break;
268       case 'A':
269                 frameList[frameCounter].id   = ID3FID_ALBUM;
270                 frameList[frameCounter].data = optarg;
271                 frameCounter++;
272                 break;
273       case 't':
274                 frameList[frameCounter].id   = ID3FID_TITLE;
275                 frameList[frameCounter].data = optarg;
276                 frameCounter++;
277                 break;
278       case 'c':
279                 frameList[frameCounter].id   = ID3FID_COMMENT;
280                 frameList[frameCounter].data = optarg;
281                 frameCounter++;
282                 break;
283       case 'g':
284           {
285                 int genre_id = 255;
286                 char *genre_str;
287                 sscanf(optarg, "%d", &genre_id);
288                 if (genre_id == 255)
289                     genre_id = GetNumFromGenre(optarg);
290                 if (genre_id == 255)
291                     genre_str = optarg;
292                 else {
293                     sprintf(tmp, "(%d)", genre_id);
294                     genre_str = tmp;
295                 }
296                 frameList[frameCounter].id   = ID3FID_CONTENTTYPE;
297                 frameList[frameCounter].data = genre_str;
298                 frameCounter++;
299           }
300                 break;
301       case 'y':
302                 frameList[frameCounter].id   = ID3FID_YEAR;
303                 frameList[frameCounter].data = optarg;
304                 frameCounter++;
305                 break;
306       case 'T':
307                 frameList[frameCounter].id   = ID3FID_TRACKNUM;
308                 frameList[frameCounter].data = optarg;
309                 frameCounter++;
310                 break;
311     // other tags
312 
313       default:
314 		std::cerr << "This isn't supposed to happen" << std::endl;
315                 exit(1);
316     }
317   }
318 
319   // loop thru the files
320   if (optind == argc)
321   {
322 	  std::cerr << "No file to work on." << std::endl;
323     exit(1);
324   }
325 
326   for (int nIndex = optind; nIndex < argc; nIndex++)
327   {
328     ID3_Tag myTag;
329     struct stat filestat;
330 
331     // std::cout << "Tagging " << argv[nIndex] << ": ";
332 
333     // fix me - not checking to see if we can link to it
334 
335 
336     if (stat(argv[nIndex], &filestat))
337     {
338       std::cerr << "Couldn't stat file '" << argv[nIndex] << "'\n";
339       break;
340     }
341 
342     /* cludgy to check if we have the proper perms */
343     fp = fopen(argv[nIndex], "r+");
344     if (fp == NULL) { /* file didn't open */
345       fprintf(stderr, "fopen: %s: ", argv[nIndex]);
346       perror("id3v2");
347       continue;
348     }
349     fclose(fp);
350 
351     size_t ret;
352     ret = myTag.Link(argv[nIndex], UpdFlags);
353 
354     // loop thru the frames we need to add/modify
355     for (ii = 0; ii < frameCounter; ii++)
356     {
357       ID3_Frame *myFrame;
358       myFrame = new ID3_Frame;
359       if (NULL == myFrame)
360       {
361          std::cout << "\nOut of memory\n" << std::endl;
362          exit(1);
363       }
364 
365       myFrame->SetID(frameList[ii].id);
366 
367       ID3_Frame *pFrame;
368       pFrame = myTag.Find(frameList[ii].id);
369 
370       switch (frameList[ii].id)
371       {
372       //  strings
373         case ID3FID_ALBUM:
374         case ID3FID_BPM:
375         case ID3FID_COMPOSER:
376         case ID3FID_CONTENTTYPE:
377         case ID3FID_COPYRIGHT:
378         case ID3FID_DATE:
379         case ID3FID_PLAYLISTDELAY:
380         case ID3FID_ENCODEDBY:
381         case ID3FID_LYRICIST:
382         case ID3FID_FILETYPE:
383         case ID3FID_TIME:
384         case ID3FID_CONTENTGROUP:
385         case ID3FID_TITLE:
386         case ID3FID_SUBTITLE:
387         case ID3FID_INITIALKEY:
388         case ID3FID_LANGUAGE:
389         case ID3FID_SONGLEN:
390         case ID3FID_MEDIATYPE:
391         case ID3FID_ORIGALBUM:
392         case ID3FID_ORIGFILENAME:
393         case ID3FID_ORIGLYRICIST:
394         case ID3FID_ORIGARTIST:
395         case ID3FID_ORIGYEAR:
396         case ID3FID_FILEOWNER:
397         case ID3FID_LEADARTIST:
398         case ID3FID_BAND:
399         case ID3FID_CONDUCTOR:
400         case ID3FID_MIXARTIST:
401         case ID3FID_PARTINSET:
402         case ID3FID_PUBLISHER:
403         case ID3FID_RECORDINGDATES:
404         case ID3FID_NETRADIOSTATION:
405         case ID3FID_NETRADIOOWNER:
406         case ID3FID_SIZE:
407         case ID3FID_ISRC:
408         case ID3FID_ENCODERSETTINGS:
409         case ID3FID_YEAR:
410         {
411           if (pFrame != NULL)
412           {
413             ID3_Frame * todel = myTag.RemoveFrame(pFrame);
414             delete todel;
415           }
416           if (strlen(frameList[ii].data) > 0) {
417             myFrame->Field(ID3FN_TEXT) = frameList[ii].data;
418             myTag.AttachFrame(myFrame);
419           }
420           break;
421         }
422         case ID3FID_TERMSOFUSE:
423         {
424           if (pFrame != NULL)
425           {
426             ID3_Frame * todel = myTag.RemoveFrame(pFrame);
427             delete todel;
428           }
429           if (strlen(frameList[ii].data) > 0) {
430             myFrame->Field(ID3FN_TEXTENC) = ID3TE_ASCII;
431             char *text;
432             text = strchr(frameList[ii].data, ':');
433             if (text == NULL)
434             {
435               myFrame->Field(ID3FN_TEXT) = frameList[ii].data;
436             } else {
437               *text = '\0';
438               text++;
439               myFrame->Field(ID3FN_LANGUAGE) = frameList[ii].data;
440               myFrame->Field(ID3FN_TEXT) = text;
441             }
442             char * test = ID3_GetString(myFrame, ID3FN_TEXT);
443             if (strlen(test) > 0) {
444               delete[] test;
445               myTag.AttachFrame(myFrame);
446             }
447             break;
448           }
449         }
450         case ID3FID_TRACKNUM:
451         {
452           // this ought to check if there is a total track number and
453           // combine it with the given track number, but it doesn't.
454           char *currentTrackNum = NULL;
455           char *newTrackNum = NULL;
456 
457           if (pFrame != NULL)
458           {
459             currentTrackNum = ID3_GetString(pFrame, ID3FN_TEXT);
460             if (*currentTrackNum == '/')
461             {
462               newTrackNum = (char *)malloc(strlen(currentTrackNum)
463                                    + strlen(frameList[ii].data));
464               strcpy(newTrackNum, frameList[ii].data);
465               strcat(newTrackNum, currentTrackNum);
466             }
467             else
468             {
469             ID3_Frame * todel = myTag.RemoveFrame(pFrame);
470             delete todel;
471             }
472           }
473 
474           myFrame->Field(ID3FN_TEXT) = frameList[ii].data;
475           myTag.AttachFrame(myFrame);
476 
477           free(newTrackNum);
478           break;
479         }
480         case ID3FID_USERTEXT:
481         {
482           if (pFrame != NULL)
483           {
484             ID3_Frame * todel = myTag.RemoveFrame(pFrame);
485             delete todel;
486           }
487 
488           // split the string at the ':' remember if no : then leave
489           // descrip empty
490           char *text;
491           text = strchr(frameList[ii].data, ':');
492           if (text == NULL)
493           {
494             myFrame->Field(ID3FN_TEXT) = frameList[ii].data;
495           } else {
496             *text = '\0';
497             text++;
498             myFrame->Field(ID3FN_DESCRIPTION) = frameList[ii].data;
499             myFrame->Field(ID3FN_TEXT) = text;
500           }
501           if (strlen(ID3_GetString(myFrame, ID3FN_TEXT)) > 0) {
502             myTag.AttachFrame(myFrame);
503           }
504 
505           break;
506         }
507         case ID3FID_COMMENT:
508         case ID3FID_UNSYNCEDLYRICS:
509         {
510           // split the string at the ':' remember if no : then leave
511           // descrip/lang empty
512           char *text;
513           text = strchr(frameList[ii].data, ':');
514           if (text == NULL)
515           {
516             myFrame->Field(ID3FN_TEXT) = frameList[ii].data;
517           } else {
518          	*text = '\0';
519           	text++;
520           	char *lang;
521           	lang = strchr(text, ':');
522           	if (lang == NULL)
523           	{
524           	  myFrame->Field(ID3FN_DESCRIPTION) = frameList[ii].data;
525           	  myFrame->Field(ID3FN_TEXT) = text;
526           	} else {
527           	  *lang = '\0';
528           	  lang++;
529           	  myFrame->Field(ID3FN_DESCRIPTION) = frameList[ii].data;
530               myFrame->Field(ID3FN_TEXT) = text;
531               myFrame->Field(ID3FN_LANGUAGE) = lang;
532             }
533           }
534           /* debug
535           std::cout << ID3_GetString(myFrame, ID3FN_DESCRIPTION) << std::endl
536                << ID3_GetString(myFrame, ID3FN_TEXT) << std::endl
537                << ID3_GetString(myFrame, ID3FN_LANGUAGE) << std::endl;
538           */
539 
540           // now try and find a comment/lyrics with the same descript
541           // and lang as what we have
542           ID3_Frame *pFirstFrame = NULL;
543           do {
544             // if pFrame is NULL, either there were no comments/lyrics
545             // to begin with, or we removed them all in the process
546             if (pFrame == NULL) break;
547 
548             if (pFirstFrame == NULL)
549             {
550               pFirstFrame = pFrame;
551             }
552 
553             char *tmp_desc = ID3_GetString(pFrame, ID3FN_DESCRIPTION);
554             char *tmp_my_desc = ID3_GetString(myFrame, ID3FN_DESCRIPTION);
555             char *tmp_lang = ID3_GetString(pFrame, ID3FN_LANGUAGE);
556             char *tmp_my_lang = ID3_GetString(myFrame, ID3FN_LANGUAGE);
557             if ((strcmp(tmp_desc, tmp_my_desc) == 0) &&
558                 (strcmp(tmp_lang, tmp_my_lang) == 0))
559             {
560               ID3_Frame * todel = myTag.RemoveFrame(pFrame);
561               delete todel;
562               if (pFrame == pFirstFrame)
563               {
564                 pFirstFrame = NULL;
565               }
566             }
567             delete [] tmp_desc;
568             delete [] tmp_my_desc;
569             delete [] tmp_lang;
570             delete [] tmp_my_lang;
571 
572             // get the next frame until it wraps around
573           } while ((pFrame = myTag.Find(frameList[ii].id)) != pFirstFrame);
574 
575           char * temp = ID3_GetString(myFrame, ID3FN_TEXT);
576           if (strlen(temp) > 0) {
577             delete[] temp;
578             myTag.AttachFrame(myFrame);
579           }
580 
581           break;
582         }
583         case ID3FID_WWWAUDIOFILE:
584         case ID3FID_WWWARTIST:
585         case ID3FID_WWWAUDIOSOURCE:
586         case ID3FID_WWWCOMMERCIALINFO:
587         case ID3FID_WWWCOPYRIGHT:
588         case ID3FID_WWWPUBLISHER:
589         case ID3FID_WWWPAYMENT:
590         case ID3FID_WWWRADIOPAGE:
591         {
592           if (pFrame != NULL)
593           {
594             char *sURL = ID3_GetString(pFrame, ID3FN_URL);
595             if (strcmp(frameList[ii].data, sURL) == 0) {
596               ID3_Frame * todel = myTag.RemoveFrame(pFrame);
597               delete todel;
598             }
599           }
600 
601           if (strlen(frameList[ii].data) > 0) {
602             myFrame->Field(ID3FN_URL) = frameList[ii].data;
603             myTag.AttachFrame(myFrame);
604           }
605 
606           break;
607 
608         }
609         case ID3FID_WWWUSER:
610         {
611           char
612             *sURL = ID3_GetString(myFrame, ID3FN_URL),
613             *sDesc = ID3_GetString(myFrame, ID3FN_DESCRIPTION);
614           std::cout << "(" << sDesc << "): " << sURL << std::endl;
615           delete [] sURL;
616           delete [] sDesc;
617           break;
618         }
619         case ID3FID_INVOLVEDPEOPLE:
620         {
621           // This isn't the right way to do it---will only get first person
622           size_t nItems = myFrame->Field(ID3FN_TEXT).GetNumTextItems();
623           for (size_t nIndex = 1; nIndex <= nItems; nIndex++)
624           {
625             char *sPeople = ID3_GetString(myFrame, ID3FN_TEXT, nIndex);
626             std::cout << sPeople;
627             delete [] sPeople;
628             if (nIndex < nItems)
629             {
630               std::cout << ", ";
631             }
632           }
633           std::cout << std::endl;
634           break;
635         }
636         case ID3FID_PICTURE:
637         {
638           char
639             *sMimeType = ID3_GetString(myFrame, ID3FN_MIMETYPE),
640             *sDesc     = ID3_GetString(myFrame, ID3FN_DESCRIPTION),
641             *sFormat   = ID3_GetString(myFrame, ID3FN_IMAGEFORMAT);
642           size_t
643             nPicType   = myFrame->Field(ID3FN_PICTURETYPE).Get(),
644             nDataSize  = myFrame->Field(ID3FN_DATA).Size();
645           std::cout << "(" << sDesc << ")[" << sFormat << ", "
646                << nPicType << "]: " << sMimeType << ", " << nDataSize
647                << " bytes" << std::endl;
648           delete [] sMimeType;
649           delete [] sDesc;
650           delete [] sFormat;
651           break;
652         }
653         case ID3FID_GENERALOBJECT:
654         {
655           char
656             *sMimeType = ID3_GetString(myFrame, ID3FN_TEXT),
657             *sDesc = ID3_GetString(myFrame, ID3FN_DESCRIPTION),
658             *sFileName = ID3_GetString(myFrame, ID3FN_FILENAME);
659           size_t
660           nDataSize = myFrame->Field(ID3FN_DATA).Size();
661           std::cout << "(" << sDesc << ")["
662               << sFileName << "]: " << sMimeType << ", " << nDataSize
663               << " bytes" << std::endl;
664           delete [] sMimeType;
665           delete [] sDesc;
666           delete [] sFileName;
667           break;
668         }
669         case ID3FID_UNIQUEFILEID:
670         {
671           if (pFrame != NULL)
672           {
673             char *sOwner = ID3_GetString(pFrame, ID3FN_TEXT);
674             size_t nDataSize = pFrame->Field(ID3FN_DATA).Size();
675             std::cout << sOwner << ", " << nDataSize
676                  << " bytes" << std::endl;
677             delete [] sOwner;
678           }
679           break;
680         }
681         case ID3FID_PLAYCOUNTER:
682         {
683           if (pFrame != NULL)
684           {
685             size_t nCounter = pFrame->Field(ID3FN_COUNTER).Get();
686             std::cout << nCounter << std::endl;
687           }
688           break;
689         }
690         case ID3FID_POPULARIMETER:
691         {
692           if (pFrame != NULL)
693           {
694             char *sEmail = ID3_GetString(pFrame, ID3FN_EMAIL);
695             size_t
696               nCounter = pFrame->Field(ID3FN_COUNTER).Get(),
697               nRating = pFrame->Field(ID3FN_RATING).Get();
698             std::cout << sEmail << ", counter="
699                  << nCounter << " rating=" << nRating;
700             delete [] sEmail;
701           }
702           break;
703         }
704         case ID3FID_CRYPTOREG:
705         case ID3FID_GROUPINGREG:
706         {
707           char *sOwner = ID3_GetString(myFrame, ID3FN_OWNER);
708           size_t
709             nSymbol = myFrame->Field(ID3FN_ID).Get(),
710             nDataSize = myFrame->Field(ID3FN_DATA).Size();
711           std::cout << "(" << nSymbol << "): " << sOwner
712                << ", " << nDataSize << " bytes";
713           break;
714         }
715         case ID3FID_AUDIOCRYPTO:
716         case ID3FID_EQUALIZATION:
717         case ID3FID_EVENTTIMING:
718         case ID3FID_CDID:
719         case ID3FID_MPEGLOOKUP:
720         case ID3FID_OWNERSHIP:
721         case ID3FID_PRIVATE:
722         case ID3FID_POSITIONSYNC:
723         case ID3FID_BUFFERSIZE:
724         case ID3FID_VOLUMEADJ:
725         case ID3FID_REVERB:
726         case ID3FID_SYNCEDLYRICS:
727         case ID3FID_SYNCEDTEMPO:
728         case ID3FID_METACRYPTO:
729         {
730           std::cout << " (unimplemented)" << std::endl;
731           break;
732         }
733         default:
734         {
735           std::cout << " frame" << std::endl;
736           break;
737         }
738       }
739     }  // steping thru frames
740 
741     myTag.Update(UpdFlags);
742 
743     /* update file with old mode */
744     if (chmod(argv[nIndex], filestat.st_mode))
745     {
746 	    std::cerr << "Couldn't reset permissions on '" << argv[nIndex] << "'\n";
747     }
748   }
749 
750   return 0;
751 }
752