]> git.treefish.org Git - usetaglib.git/blob - usetaglib.cpp
Added backslash escape of equal signs in tags.
[usetaglib.git] / usetaglib.cpp
1 /* Copyright (C) 2015 Alexander Schmidt <alex@treefish.org>
2  *
3  * This program is free software: you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation, either version 3 of the License, or
6  * (at your option) any later version.
7  * 
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License
14  * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
15  */
16
17 #include <iostream>
18 #include <iomanip>
19 #include <iostream>
20 #include <taglib/fileref.h>
21 #include <taglib/tag.h>
22 #include <taglib/tpropertymap.h>
23 #include <taglib/tstringlist.h>
24 #include <getopt.h>
25 #include <vector>
26 #include <sstream>
27
28 using namespace std;
29
30 enum action {LIST, REPLACE, INSERT, ERASE, AUDIO};
31 typedef pair<action,string> actionpair;
32 typedef vector<actionpair> actionqueue;
33 typedef pair<TagLib::String,TagLib::StringList> keyandvalues;
34
35 keyandvalues toKeyAndValues (const string &rawstring)
36 {
37   stringstream tmpss;
38   TagLib::String key;
39   TagLib::StringList values;
40   int isplit=0;
41   
42   for (int ipos=0; ipos < rawstring.length(); ipos++) {
43     if ( rawstring[ipos] == '\\' ) {
44       switch (rawstring[ipos+1]) {
45       case '\\':
46         tmpss << '\\';
47         break;
48       case '=':
49         tmpss << '=';
50         break;
51       default:
52         tmpss << '\\' << rawstring[ipos+1];
53         break;
54       }
55       ipos++;
56     }
57     else
58       if ( rawstring[ipos] == '=' ) {
59         if ( isplit == 0 ) 
60           key = tmpss.str();
61         else
62           values.append(tmpss.str());
63         isplit++;
64         tmpss.str("");
65       }
66       else
67         tmpss << rawstring[ipos];
68   }
69
70   if (isplit==0)
71     key = tmpss.str();
72   else
73     values.append(tmpss.str());
74   
75   return make_pair(key, values);
76 }
77
78 void action_eraseTag (TagLib::PropertyMap &propmap, const string &key)
79 {
80   propmap.erase(key);
81 }
82
83 void action_replaceTag (TagLib::PropertyMap &propmap, const keyandvalues &keyAndValues)
84 {
85   propmap.replace(keyAndValues.first, keyAndValues.second);
86 }
87
88 void action_insertTag (TagLib::PropertyMap &propmap, const keyandvalues &keyAndValues)
89 {
90   propmap.insert(keyAndValues.first, keyAndValues.second);
91 }
92
93 int action_printTags (const TagLib::FileRef f, TagLib::PropertyMap &propmap)
94 {
95   if(f.tag()) {
96     unsigned int longest = 0;
97     for(TagLib::PropertyMap::ConstIterator i = propmap.begin(); i != propmap.end(); ++i) {
98       if (i->first.size() > longest) {
99         longest = i->first.size();
100       }
101     }
102     cout << " \\_____/ TAGS \\_____ _ _ _" << endl;
103     for(TagLib::PropertyMap::ConstIterator i = propmap.begin(); i != propmap.end(); ++i) {
104       for(TagLib::StringList::ConstIterator j = i->second.begin(); j != i->second.end(); ++j) {
105         cout << i->first << "=" << *j << endl;
106       }
107     }
108     return 0;
109   }
110   else
111     return 1;
112 }
113
114 int action_printAudio (const TagLib::FileRef f)
115 {
116   if(f.audioProperties()) {
117     TagLib::AudioProperties *properties = f.audioProperties();
118     int seconds = properties->length() % 60;
119     int minutes = (properties->length() - seconds) / 60;
120     cout << " \\_____/ AUDIO PROPERTIES \\_____ _ _ _" << endl;
121     cout << "BITRATE=" << properties->bitrate() << endl;
122     cout << "SAMPLERATE=" << properties->sampleRate() << endl;
123     cout << "CHANNELS=" << properties->channels() << endl;
124     cout << "LENGTH=" << minutes << ":" << setfill('0') << setw(2) << seconds << endl;
125     return 0;
126   }
127   else
128     return 1;
129 }
130
131 void printHelp ()
132 {  
133   cout <<
134     "Usage: usetaglib [ACTION]... [FILE]...\n"    
135     "Read and edit meta-data of audio formats supported by taglib.\n"
136     "Multiple ACTIONS and FILES may be given in arbitrary order.\n"
137     "\n"
138     "-h, --help      show help\n"
139     "-H, --longhelp  show long help\n"
140     "\n"
141     "ACTIONS\n"
142     "  If multiple actions are specified they are executed in given order.\n"
143     "\n"
144     "  -l, --list                    list all tags (implicit if no action given)\n"
145     "  -a, --audio                   show audio properties\n"
146     "  -e, --erase=TAGNAME           erase tag TAGNAME\n"
147     "  -r, --replace=TAGNAME=TAGVAL  replace tag TAGNAME with value TAGVAL\n"
148     "  -i, --insert=TAGNAME=TAGVAL   insert tag TAGNAME with value TAGVAL\n";
149 }
150
151 void printExtraHelp ()
152 {
153   cout <<
154     "\n"
155     "TAGNAME\n"
156     "  TAGNAME is a media format independent id encoding the type of a tag.\n"
157     "  Note that also in --list output, format specific tag ids are translated\n"
158     "  to unified TAGNAMES.\n"
159     "\n"
160     "  Some \"well-known\" tags you might want to use are:\n"
161     "  TITLE ALBUM ARTIST ALBUMARTIST SUBTITLE TRACKNUMBER DISCNUMBER DATE\n"
162     "  ORIGINALDATE GENRE COMMENT TITLESORT ALBUMSORT ARTISTSORT\n"
163     "  ALBUMARTISTSORT COMPOSER LYRICIST CONDUCTOR REMIXER PERFORMER ISRC ASIN\n"
164     "  BPM COPYRIGHT ENCODEDBY MOOD COMMENT MEDIA LABEL CATALOGNUMBER BARCODE\n"
165     "\n"
166     "TAGVAL\n"
167     "  TAGVAL has to be either a single string or a list of strings separated\n"
168     "  by equal signs (=). If a list is given, multiple tags of type TAGNAME\n"
169     "  will be created and set to the respective values given by the list.\n"
170     "  Note that equal signs have to be escaped with a leading backslash (\\=),\n"
171     "  if they shall not be interpreted as list separators.\n"
172     "\n"
173     "EXAMPLES\n"
174     "  usetaglib file.ogg\n"
175     "  usetaglib -e ALBUM file.flac\n"
176     "  usetaglib -r \"ALBUM=New Album\" -i ARTIST=Horst=Hubert file.mp3\n"
177     "  usetaglib -r ARTIST=Horst -l file1.ogg file2.mp3\n"
178     "  usetaglib -i \"ALBUMARTIST=Horst und Hubert\" file.ogg\n"
179     "  usetaglib --insert=\"ALBUMARTIST=Horst und Hubert\" file.ogg\n"
180     "  usetaglib --replace='ARTIST=This Band \\= Great' file.ogg\n";
181 }
182   
183 int main(int argc, char *argv[])
184 {
185   int c;
186   actionqueue requestedActions;
187   
188   while (1)
189     {
190       static struct option long_options[] =
191         {
192           {"help",      no_argument,       0, 'h'},
193           {"longhelp",  no_argument,       0, 'H'},
194           {"list",      no_argument,       0, 'l'},
195           {"audio",     no_argument,       0, 'a'},
196           {"insert",    required_argument, 0, 'i'},
197           {"erase",     required_argument, 0, 'e'},
198           {"replace",   required_argument, 0, 'r'},
199           {0, 0, 0, 0}
200         };
201
202       int option_index = 0;      
203       c = getopt_long (argc, argv, "hHlai:e:r:",
204                        long_options, &option_index);
205
206       if (c == -1)
207         break;
208
209       switch (c)
210         {
211         case 0:
212           if (long_options[option_index].flag != 0)
213             break;
214         case 'h':
215           printHelp();
216           return 0;
217           break;
218         case 'H':
219           printHelp();
220           printExtraHelp();
221           return 0;
222           break;
223         case 'l':
224           requestedActions.push_back( make_pair(LIST, "") );
225           break;
226         case 'a':
227           requestedActions.push_back( make_pair(AUDIO, "") );
228           break;
229         case 'i':
230           requestedActions.push_back( make_pair(INSERT, optarg) );
231           break;
232         case 'e':
233           requestedActions.push_back( make_pair(ERASE, optarg) );
234           break;
235         case 'r':
236           requestedActions.push_back( make_pair(REPLACE, optarg) );
237           break;
238         case '?':
239           break;
240         default:
241           abort ();
242         }
243     }
244
245   if ( optind == argc ) {
246     printHelp();
247     return 0;
248   }
249   
250   if (requestedActions.size() == 0)
251     requestedActions.push_back( make_pair(LIST, "") );
252         
253   for(int i = optind; i < argc; i++) {
254     cout << " _________________________________________ _ _ _" << endl;
255     cout << "/" << endl;
256     cout << "  " << argv[i] << endl;
257     cout << "\\_________________________________________ _ _ _" << endl;
258     
259     TagLib::FileRef f(argv[i]);
260
261     if(f.isNull())
262       continue;
263
264     TagLib::PropertyMap propmap = f.file()->properties();    
265     bool FCHANGED = false;
266     
267     for (actionqueue::iterator actit = requestedActions.begin(); actit != requestedActions.end(); ++actit) {
268       switch (actit->first) {
269       case LIST:
270         action_printTags(f, propmap);
271         break;
272       case AUDIO:
273         action_printAudio(f);
274         break;
275       case ERASE:
276         action_eraseTag(propmap, actit->second);
277         FCHANGED = true;
278         break;
279       case REPLACE:
280         action_replaceTag(propmap, toKeyAndValues(actit->second));
281         FCHANGED = true;
282         break;
283       case INSERT:
284         action_insertTag(propmap, toKeyAndValues(actit->second));
285         FCHANGED = true;
286         break;
287       }
288     }
289
290     if (FCHANGED) {
291       f.file()->setProperties(propmap);
292       f.file()->save();
293     }
294   }
295   
296   return 0;
297 }