]> git.treefish.org Git - usetaglib.git/blobdiff - usetaglib.cpp
Added backslash escape of equal signs in tags.
[usetaglib.git] / usetaglib.cpp
index 7909009ca1895dc40cc37fd408b82129876317ab..d8d51ed399f07ae4d3eb5b5bc196d428c822cb72 100644 (file)
 #include <taglib/tstringlist.h>
 #include <getopt.h>
 #include <vector>
 #include <taglib/tstringlist.h>
 #include <getopt.h>
 #include <vector>
+#include <sstream>
 
 using namespace std;
 
 enum action {LIST, REPLACE, INSERT, ERASE, AUDIO};
 typedef pair<action,string> actionpair;
 typedef vector<actionpair> actionqueue;
 
 using namespace std;
 
 enum action {LIST, REPLACE, INSERT, ERASE, AUDIO};
 typedef pair<action,string> actionpair;
 typedef vector<actionpair> actionqueue;
-typedef pair<string,string> tagpair;
+typedef pair<TagLib::String,TagLib::StringList> keyandvalues;
 
 
-TagLib::StringList argToStringList (const string &rawtagarg)
+keyandvalues toKeyAndValues (const string &rawstring)
 {
 {
-  TagLib::StringList newlist;
-
-  size_t delpos = 0;
-  while (1) {
-    size_t nextdelpos = rawtagarg.find('=', delpos+1);
-    if (nextdelpos == -1)
-      break;
-    newlist.append(rawtagarg.substr(delpos,nextdelpos-delpos));
-    delpos = nextdelpos+1;
+  stringstream tmpss;
+  TagLib::String key;
+  TagLib::StringList values;
+  int isplit=0;
+  
+  for (int ipos=0; ipos < rawstring.length(); ipos++) {
+    if ( rawstring[ipos] == '\\' ) {
+      switch (rawstring[ipos+1]) {
+      case '\\':
+       tmpss << '\\';
+       break;
+      case '=':
+       tmpss << '=';
+       break;
+      default:
+       tmpss << '\\' << rawstring[ipos+1];
+       break;
+      }
+      ipos++;
+    }
+    else
+      if ( rawstring[ipos] == '=' ) {
+       if ( isplit == 0 ) 
+         key = tmpss.str();
+       else
+         values.append(tmpss.str());
+       isplit++;
+       tmpss.str("");
+      }
+      else
+       tmpss << rawstring[ipos];
   }
   }
-  newlist.append(rawtagarg.substr(delpos,string::npos));  
 
 
-  return newlist;
-}
-
-tagpair splitToTagPair (const string &rawarg)
-{
-  const size_t delpos = rawarg.find('=');
-  return make_pair(rawarg.substr(0, delpos), rawarg.substr(delpos+1, string::npos));
+  if (isplit==0)
+    key = tmpss.str();
+  else
+    values.append(tmpss.str());
+  
+  return make_pair(key, values);
 }
 
 }
 
-void action_eraseTag (const TagLib::FileRef f, const string &key)
+void action_eraseTag (TagLib::PropertyMap &propmap, const string &key)
 {
 {
-  TagLib::PropertyMap propmap = f.file()->properties();
   propmap.erase(key);
   propmap.erase(key);
-  f.file()->setProperties(propmap);
 }
 
 }
 
-void action_replaceTag (const TagLib::FileRef f, const tagpair &tagPair)
+void action_replaceTag (TagLib::PropertyMap &propmap, const keyandvalues &keyAndValues)
 {
 {
-  TagLib::PropertyMap propmap = f.file()->properties();
-  propmap.replace(tagPair.first, argToStringList(tagPair.second));
-  f.file()->setProperties(propmap);
+  propmap.replace(keyAndValues.first, keyAndValues.second);
 }
 
 }
 
-void action_insertTag (const TagLib::FileRef f, const tagpair &tagPair)
+void action_insertTag (TagLib::PropertyMap &propmap, const keyandvalues &keyAndValues)
 {
 {
-  TagLib::PropertyMap propmap = f.file()->properties();
-  propmap.insert(tagPair.first, argToStringList(tagPair.second));
-  f.file()->setProperties(propmap);
+  propmap.insert(keyAndValues.first, keyAndValues.second);
 }
 
 }
 
-int action_printTags (const TagLib::FileRef f)
+int action_printTags (const TagLib::FileRef f, TagLib::PropertyMap &propmap)
 {
   if(f.tag()) {
 {
   if(f.tag()) {
-    TagLib::PropertyMap tags = f.file()->properties();
     unsigned int longest = 0;
     unsigned int longest = 0;
-    for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) {
+    for(TagLib::PropertyMap::ConstIterator i = propmap.begin(); i != propmap.end(); ++i) {
       if (i->first.size() > longest) {
        longest = i->first.size();
       }
     }
       if (i->first.size() > longest) {
        longest = i->first.size();
       }
     }
-    cout << "-- TAG (properties) --" << endl;
-    for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) {
+    cout << " \\_____/ TAGS \\_____ _ _ _" << endl;
+    for(TagLib::PropertyMap::ConstIterator i = propmap.begin(); i != propmap.end(); ++i) {
       for(TagLib::StringList::ConstIterator j = i->second.begin(); j != i->second.end(); ++j) {
        cout << i->first << "=" << *j << endl;
       }
       for(TagLib::StringList::ConstIterator j = i->second.begin(); j != i->second.end(); ++j) {
        cout << i->first << "=" << *j << endl;
       }
@@ -103,7 +117,7 @@ int action_printAudio (const TagLib::FileRef f)
     TagLib::AudioProperties *properties = f.audioProperties();
     int seconds = properties->length() % 60;
     int minutes = (properties->length() - seconds) / 60;
     TagLib::AudioProperties *properties = f.audioProperties();
     int seconds = properties->length() % 60;
     int minutes = (properties->length() - seconds) / 60;
-    cout << "-- AUDIO --" << endl;
+    cout << " \\_____/ AUDIO PROPERTIES \\_____ _ _ _" << endl;
     cout << "BITRATE=" << properties->bitrate() << endl;
     cout << "SAMPLERATE=" << properties->sampleRate() << endl;
     cout << "CHANNELS=" << properties->channels() << endl;
     cout << "BITRATE=" << properties->bitrate() << endl;
     cout << "SAMPLERATE=" << properties->sampleRate() << endl;
     cout << "CHANNELS=" << properties->channels() << endl;
@@ -115,32 +129,55 @@ int action_printAudio (const TagLib::FileRef f)
 }
 
 void printHelp ()
 }
 
 void printHelp ()
+{  
+  cout <<
+    "Usage: usetaglib [ACTION]... [FILE]...\n"    
+    "Read and edit meta-data of audio formats supported by taglib.\n"
+    "Multiple ACTIONS and FILES may be given in arbitrary order.\n"
+    "\n"
+    "-h, --help      show help\n"
+    "-H, --longhelp  show long help\n"
+    "\n"
+    "ACTIONS\n"
+    "  If multiple actions are specified they are executed in given order.\n"
+    "\n"
+    "  -l, --list                    list all tags (implicit if no action given)\n"
+    "  -a, --audio                   show audio properties\n"
+    "  -e, --erase=TAGNAME           erase tag TAGNAME\n"
+    "  -r, --replace=TAGNAME=TAGVAL  replace tag TAGNAME with value TAGVAL\n"
+    "  -i, --insert=TAGNAME=TAGVAL   insert tag TAGNAME with value TAGVAL\n";
+}
+
+void printExtraHelp ()
 {
 {
-  cout << "Usage: usetaglib [ACTION]... [FILE]..." << endl;
-  cout << "List and edit tags on mediafiles in formats supported by libtag." << endl;
-  cout << endl;
-  cout << "-h, --help   Show this help" << endl;
-  cout << endl;
-  cout << "ACTIONS" << endl;
-  cout << setfill(' ') << setw(37) << left << "  -l, --list"
-       << "list all tags (implicit if no action specified)"<< endl;
-  cout << setfill(' ') << setw(37) << left << "  -a, --listaudio"
-       << "show audio information"<< endl;
-  cout << setfill(' ') << setw(37) << left << "  -e, --erase=TAGNAME"
-       << "erase tag TAGNAME"<< endl;
-  cout << setfill(' ') << setw(37) << left << "  -r, --replace=TAGNAME=TAGVALSPEC"
-       << "replace tag TAGNAME with value TAGVALSPEC"<< endl;
-  cout << setfill(' ') << setw(37) << left << "  -i, --insert=TAGNAME=TAGVALSPEC"
-       << "insert value TAGVALSPEC for tag TAGNAME"<< endl;
-  cout << endl;
-  cout << "TAGVALSPEC" << endl;
-  cout << "  a list of values separated by '=': val1=val2=val3..." << endl;
-  cout << endl;
-  cout << "EXAMPLES" << endl;
-  cout << "  usetaglib file.ogg" << endl;
-  cout << "  usetaglib -e ALBUM file.flac" << endl;
-  cout << "  usetaglib -i ARTIST=Horst=Hubert file.mp3" << endl;
-  cout << "  usetaglib -r ARTIST=Horst file.ogg" << endl;
+  cout <<
+    "\n"
+    "TAGNAME\n"
+    "  TAGNAME is a media format independent id encoding the type of a tag.\n"
+    "  Note that also in --list output, format specific tag ids are translated\n"
+    "  to unified TAGNAMES.\n"
+    "\n"
+    "  Some \"well-known\" tags you might want to use are:\n"
+    "  TITLE ALBUM ARTIST ALBUMARTIST SUBTITLE TRACKNUMBER DISCNUMBER DATE\n"
+    "  ORIGINALDATE GENRE COMMENT TITLESORT ALBUMSORT ARTISTSORT\n"
+    "  ALBUMARTISTSORT COMPOSER LYRICIST CONDUCTOR REMIXER PERFORMER ISRC ASIN\n"
+    "  BPM COPYRIGHT ENCODEDBY MOOD COMMENT MEDIA LABEL CATALOGNUMBER BARCODE\n"
+    "\n"
+    "TAGVAL\n"
+    "  TAGVAL has to be either a single string or a list of strings separated\n"
+    "  by equal signs (=). If a list is given, multiple tags of type TAGNAME\n"
+    "  will be created and set to the respective values given by the list.\n"
+    "  Note that equal signs have to be escaped with a leading backslash (\\=),\n"
+    "  if they shall not be interpreted as list separators.\n"
+    "\n"
+    "EXAMPLES\n"
+    "  usetaglib file.ogg\n"
+    "  usetaglib -e ALBUM file.flac\n"
+    "  usetaglib -r \"ALBUM=New Album\" -i ARTIST=Horst=Hubert file.mp3\n"
+    "  usetaglib -r ARTIST=Horst -l file1.ogg file2.mp3\n"
+    "  usetaglib -i \"ALBUMARTIST=Horst und Hubert\" file.ogg\n"
+    "  usetaglib --insert=\"ALBUMARTIST=Horst und Hubert\" file.ogg\n"
+    "  usetaglib --replace='ARTIST=This Band \\= Great' file.ogg\n";
 }
   
 int main(int argc, char *argv[])
 }
   
 int main(int argc, char *argv[])
@@ -153,8 +190,9 @@ int main(int argc, char *argv[])
       static struct option long_options[] =
         {
          {"help",      no_argument,       0, 'h'},
       static struct option long_options[] =
         {
          {"help",      no_argument,       0, 'h'},
+         {"longhelp",  no_argument,       0, 'H'},
           {"list",      no_argument,       0, 'l'},
           {"list",      no_argument,       0, 'l'},
-         {"listaudio", no_argument,       0, 'a'},
+         {"audio",     no_argument,       0, 'a'},
           {"insert",    required_argument, 0, 'i'},
           {"erase",     required_argument, 0, 'e'},
           {"replace",   required_argument, 0, 'r'},
           {"insert",    required_argument, 0, 'i'},
           {"erase",     required_argument, 0, 'e'},
           {"replace",   required_argument, 0, 'r'},
@@ -162,7 +200,7 @@ int main(int argc, char *argv[])
         };
 
       int option_index = 0;      
         };
 
       int option_index = 0;      
-      c = getopt_long (argc, argv, "hlai:e:r:",
+      c = getopt_long (argc, argv, "hHlai:e:r:",
                        long_options, &option_index);
 
       if (c == -1)
                        long_options, &option_index);
 
       if (c == -1)
@@ -173,35 +211,32 @@ int main(int argc, char *argv[])
         case 0:
           if (long_options[option_index].flag != 0)
             break;
         case 0:
           if (long_options[option_index].flag != 0)
             break;
-
        case 'h':
          printHelp();
          return 0;
           break;
        case 'h':
          printHelp();
          return 0;
           break;
-         
+       case 'H':
+         printHelp();
+         printExtraHelp();
+         return 0;
+          break;
         case 'l':
          requestedActions.push_back( make_pair(LIST, "") );
           break;
         case 'l':
          requestedActions.push_back( make_pair(LIST, "") );
           break;
-
        case 'a':
          requestedActions.push_back( make_pair(AUDIO, "") );
           break;
        case 'a':
          requestedActions.push_back( make_pair(AUDIO, "") );
           break;
-
         case 'i':
          requestedActions.push_back( make_pair(INSERT, optarg) );
           break;
         case 'i':
          requestedActions.push_back( make_pair(INSERT, optarg) );
           break;
-
         case 'e':
          requestedActions.push_back( make_pair(ERASE, optarg) );
           break;
         case 'e':
          requestedActions.push_back( make_pair(ERASE, optarg) );
           break;
-
         case 'r':
          requestedActions.push_back( make_pair(REPLACE, optarg) );
           break;
         case 'r':
          requestedActions.push_back( make_pair(REPLACE, optarg) );
           break;
-
         case '?':
           break;
         case '?':
           break;
-
         default:
           abort ();
         }
         default:
           abort ();
         }
@@ -216,37 +251,46 @@ int main(int argc, char *argv[])
     requestedActions.push_back( make_pair(LIST, "") );
         
   for(int i = optind; i < argc; i++) {
     requestedActions.push_back( make_pair(LIST, "") );
         
   for(int i = optind; i < argc; i++) {
-    cout << "******************** \"" << argv[i] << "\" ********************" << endl;
+    cout << " _________________________________________ _ _ _" << endl;
+    cout << "/" << endl;
+    cout << "  " << argv[i] << endl;
+    cout << "\\_________________________________________ _ _ _" << endl;
+    
     TagLib::FileRef f(argv[i]);
 
     if(f.isNull())
       continue;
     TagLib::FileRef f(argv[i]);
 
     if(f.isNull())
       continue;
+
+    TagLib::PropertyMap propmap = f.file()->properties();    
+    bool FCHANGED = false;
     
     for (actionqueue::iterator actit = requestedActions.begin(); actit != requestedActions.end(); ++actit) {
       switch (actit->first) {
       case LIST:
     
     for (actionqueue::iterator actit = requestedActions.begin(); actit != requestedActions.end(); ++actit) {
       switch (actit->first) {
       case LIST:
-       action_printTags(f);
+       action_printTags(f, propmap);
        break;
        break;
-       
       case AUDIO:
        action_printAudio(f);
        break;
       case AUDIO:
        action_printAudio(f);
        break;
-       
       case ERASE:
       case ERASE:
-       action_eraseTag(f, actit->second);
+       action_eraseTag(propmap, actit->second);
+       FCHANGED = true;
        break;
        break;
-       
       case REPLACE:
       case REPLACE:
-       action_replaceTag(f, splitToTagPair(actit->second));
+       action_replaceTag(propmap, toKeyAndValues(actit->second));
+       FCHANGED = true;
        break;
        break;
-
       case INSERT:
       case INSERT:
-       action_insertTag(f, splitToTagPair(actit->second));
+       action_insertTag(propmap, toKeyAndValues(actit->second));
+       FCHANGED = true;
        break;
       }
     }
        break;
       }
     }
-    
-    f.file()->save();
+
+    if (FCHANGED) {
+      f.file()->setProperties(propmap);
+      f.file()->save();
+    }
   }
   
   return 0;
   }
   
   return 0;