-/*
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
+/* Copyright (C) 2015 Alexander Schmidt <alex@treefish.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
#include <iostream>
#include <iomanip>
#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;
-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);
- 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()) {
- TagLib::PropertyMap tags = f.file()->properties();
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();
}
}
- 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;
}
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;
}
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(45) << left << " -l, --list"
- << "list all tags (implicit if no action specified)"<< endl;
- cout << setfill(' ') << setw(45) << left << " -e, --erase=TAGNAME"
- << "erase tag with name TAGNAME"<< endl;
- cout << setfill(' ') << setw(45) << left << " -r, --replace=TAGNAME=TAGVAL[=TAGVAL...]"
- << "replace tag TAGNAME with list of values TAGVAL"<< endl;
- cout << setfill(' ') << setw(45) << left << " -i, --insert=TAGNAME=TAGVAL[=TAGVAL...]"
- << "insert list of values TAGVAL for tag TAGNAME"<< 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[])
static struct option long_options[] =
{
{"help", no_argument, 0, 'h'},
+ {"longhelp", no_argument, 0, 'H'},
{"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'},
};
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)
case 0:
if (long_options[option_index].flag != 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 'a':
requestedActions.push_back( make_pair(AUDIO, "") );
break;
-
case 'i':
requestedActions.push_back( make_pair(INSERT, optarg) );
break;
-
case 'e':
requestedActions.push_back( make_pair(ERASE, optarg) );
break;
-
case 'r':
requestedActions.push_back( make_pair(REPLACE, optarg) );
break;
-
case '?':
break;
-
default:
abort ();
}
}
+ if ( optind == argc ) {
+ printHelp();
+ return 0;
+ }
+
if (requestedActions.size() == 0)
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::PropertyMap propmap = f.file()->properties();
+ bool FCHANGED = false;
for (actionqueue::iterator actit = requestedActions.begin(); actit != requestedActions.end(); ++actit) {
switch (actit->first) {
case LIST:
- action_printTags(f);
+ action_printTags(f, propmap);
break;
-
case AUDIO:
action_printAudio(f);
break;
-
case ERASE:
- action_eraseTag(f, actit->second);
+ action_eraseTag(propmap, actit->second);
+ FCHANGED = true;
break;
-
case REPLACE:
- action_replaceTag(f, splitToTagPair(actit->second));
+ action_replaceTag(propmap, toKeyAndValues(actit->second));
+ FCHANGED = true;
break;
-
case INSERT:
- action_insertTag(f, splitToTagPair(actit->second));
+ action_insertTag(propmap, toKeyAndValues(actit->second));
+ FCHANGED = true;
break;
}
}
-
- f.file()->save();
+
+ if (FCHANGED) {
+ f.file()->setProperties(propmap);
+ f.file()->save();
+ }
}
return 0;