Audacity 3.2.0
Tags.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Tags.cpp
6
7 Dominic Mazzoni
8
9*******************************************************************//****************************************************************//*******************************************************************/
32
33
34#include "Tags.h"
35
36// For compilers that support precompilation, includes "wx/wx.h".
37#include <wx/wxprec.h>
38
39#include <wx/setup.h> // for wxUSE_* macros
40#include <wx/log.h>
41
42#ifndef WX_PRECOMP
43// Include your minimal set of headers here, or wx.h
44#endif
45
46#include "FileNames.h"
47#include "Prefs.h"
48#include "Project.h"
49
50static const wxChar *DefaultGenres[] =
51{
52 wxT("Blues"),
53 wxT("Classic Rock"),
54 wxT("Country"),
55 wxT("Dance"),
56 wxT("Disco"),
57 wxT("Funk"),
58 wxT("Grunge"),
59 wxT("Hip-Hop"),
60 wxT("Jazz"),
61 wxT("Metal"),
62 wxT("New Age"),
63 wxT("Oldies"),
64 wxT("Other"),
65 wxT("Pop"),
66 wxT("R&B"),
67 wxT("Rap"),
68 wxT("Reggae"),
69 wxT("Rock"),
70 wxT("Techno"),
71 wxT("Industrial"),
72 wxT("Alternative"),
73 wxT("Ska"),
74 wxT("Death Metal"),
75 wxT("Pranks"),
76 wxT("Soundtrack"),
77 wxT("Euro-Techno"),
78 wxT("Ambient"),
79 wxT("Trip-Hop"),
80 wxT("Vocal"),
81 wxT("Jazz+Funk"),
82 wxT("Fusion"),
83 wxT("Trance"),
84 wxT("Classical"),
85 wxT("Instrumental"),
86 wxT("Acid"),
87 wxT("House"),
88 wxT("Game"),
89 wxT("Sound Clip"),
90 wxT("Gospel"),
91 wxT("Noise"),
92 wxT("Alt. Rock"),
93 wxT("Bass"),
94 wxT("Soul"),
95 wxT("Punk"),
96 wxT("Space"),
97 wxT("Meditative"),
98 wxT("Instrumental Pop"),
99 wxT("Instrumental Rock"),
100 wxT("Ethnic"),
101 wxT("Gothic"),
102 wxT("Darkwave"),
103 wxT("Techno-Industrial"),
104 wxT("Electronic"),
105 wxT("Pop-Folk"),
106 wxT("Eurodance"),
107 wxT("Dream"),
108 wxT("Southern Rock"),
109 wxT("Comedy"),
110 wxT("Cult"),
111 wxT("Gangsta Rap"),
112 wxT("Top 40"),
113 wxT("Christian Rap"),
114 wxT("Pop/Funk"),
115 wxT("Jungle"),
116 wxT("Native American"),
117 wxT("Cabaret"),
118 wxT("New Wave"),
119 wxT("Psychedelic"),
120 wxT("Rave"),
121 wxT("Showtunes"),
122 wxT("Trailer"),
123 wxT("Lo-Fi"),
124 wxT("Tribal"),
125 wxT("Acid Punk"),
126 wxT("Acid Jazz"),
127 wxT("Polka"),
128 wxT("Retro"),
129 wxT("Musical"),
130 wxT("Rock & Roll"),
131 wxT("Hard Rock"),
132 wxT("Folk"),
133 wxT("Folk/Rock"),
134 wxT("National Folk"),
135 wxT("Swing"),
136 wxT("Fast-Fusion"),
137 wxT("Bebob"),
138 wxT("Latin"),
139 wxT("Revival"),
140 wxT("Celtic"),
141 wxT("Bluegrass"),
142 wxT("Avantgarde"),
143 wxT("Gothic Rock"),
144 wxT("Progressive Rock"),
145 wxT("Psychedelic Rock"),
146 wxT("Symphonic Rock"),
147 wxT("Slow Rock"),
148 wxT("Big Band"),
149 wxT("Chorus"),
150 wxT("Easy Listening"),
151 wxT("Acoustic"),
152 wxT("Humour"),
153 wxT("Speech"),
154 wxT("Chanson"),
155 wxT("Opera"),
156 wxT("Chamber Music"),
157 wxT("Sonata"),
158 wxT("Symphony"),
159 wxT("Booty Bass"),
160 wxT("Primus"),
161 wxT("Porn Groove"),
162 wxT("Satire"),
163 wxT("Slow Jam"),
164 wxT("Club"),
165 wxT("Tango"),
166 wxT("Samba"),
167 wxT("Folklore"),
168 wxT("Ballad"),
169 wxT("Power Ballad"),
170 wxT("Rhythmic Soul"),
171 wxT("Freestyle"),
172 wxT("Duet"),
173 wxT("Punk Rock"),
174 wxT("Drum Solo"),
175 wxT("A Cappella"),
176 wxT("Euro-House"),
177 wxT("Dance Hall"),
178 wxT("Goa"),
179 wxT("Drum & Bass"),
180 wxT("Club-House"),
181 wxT("Hardcore"),
182 wxT("Terror"),
183 wxT("Indie"),
184 wxT("BritPop"),
185
186 // Standard name is offensive (see "http://www.audacityteam.org/forum/viewtopic.php?f=11&t=3924").
187 wxT("Offensive"), // wxT("Negerpunk"),
188
189 wxT("Polsk Punk"),
190 wxT("Beat"),
191 wxT("Christian Gangsta Rap"),
192 wxT("Heavy Metal"),
193 wxT("Black Metal"),
194 wxT("Crossover"),
195 wxT("Contemporary Christian"),
196 wxT("Christian Rock"),
197 wxT("Merengue"),
198 wxT("Salsa"),
199 wxT("Thrash Metal"),
200 wxT("Anime"),
201 wxT("JPop"),
202 wxT("Synthpop")
203};
204
206 "tags",
207 []( AudacityProject &project ){ return &Tags::Get( project ); }
208};
209
211 [](AudacityProject &){ return std::make_shared< Tags >(); }
212};
213
215{
216 return project.AttachedObjects::Get< Tags >( key );
217}
218
219const Tags &Tags::Get( const AudacityProject &project )
220{
221 return Get( const_cast< AudacityProject & >( project ) );
222}
223
224Tags &Tags::Set( AudacityProject &project, const std::shared_ptr< Tags > &tags )
225{
226 auto &result = *tags;
227 project.AttachedObjects::Assign( key, tags );
228 return result;
229}
230
232{
233 LoadDefaults();
234 LoadGenres();
235}
236
238{
239}
240
241std::shared_ptr<Tags> Tags::Duplicate() const
242{
243 return std::make_shared<Tags>(*this);
244}
245
246void Tags::Merge( const Tags &other )
247{
248 for ( auto &pair : other.mMap ) {
249 SetTag( pair.first, pair.second );
250 }
251}
252
254{
255 mXref.clear();
256 mXref = src.mXref;
257 mMap.clear();
258 mMap = src.mMap;
259
260 mGenres.clear();
261 mGenres = src.mGenres;
262
263 return *this;
264}
265
267{
268 wxString path;
269 wxString name;
270 wxString value;
271 long ndx;
272 bool cont;
273
274 // Set the parent group
275 path = gPrefs->GetPath();
276 gPrefs->SetPath(wxT("/Tags"));
277
278 // Process all entries in the group
279 cont = gPrefs->GetFirstEntry(name, ndx);
280 while (cont) {
281 gPrefs->Read(name, &value, wxT(""));
282
283 if (name == wxT("ID3V2")) {
284 // LLL: This is obsolute, but it must be handled and ignored.
285 }
286 else {
287 SetTag(name, value);
288 }
289
290 cont = gPrefs->GetNextEntry(name, ndx);
291 }
292
293 // Restore original group
294 gPrefs->SetPath(path);
295}
296
298{
299 // At least one of these should be filled in, otherwise
300 // it's assumed that the tags have not been set...
302 return false;
303 }
304
305 return true;
306}
307
309{
310 mXref.clear();
311 mMap.clear();
312}
313
314namespace {
315 bool EqualMaps(const TagMap &map1, const TagMap &map2)
316 {
317 // Maps are unordered, hash maps; can't just iterate in tandem and
318 // compare.
319 if (map1.size() != map2.size())
320 return false;
321
322 for (const auto &pair : map2) {
323 auto iter = map1.find(pair.first);
324 if (iter == map1.end() || iter->second != pair.second)
325 return false;
326 }
327
328 return true;
329 }
330}
331
332bool operator== (const Tags &lhs, const Tags &rhs)
333{
334 if (!EqualMaps(lhs.mXref, rhs.mXref))
335 return false;
336
337 if (!EqualMaps(lhs.mMap, rhs.mMap))
338 return false;
339
340 return lhs.mGenres == rhs.mGenres;
341}
342
344{
345 return mGenres.size();
346}
347
349{
350 mGenres.clear();
351 for (size_t i = 0; i < WXSIZEOF(DefaultGenres); i++) {
352 mGenres.push_back(DefaultGenres[i]);
353 }
354}
355
357{
358 wxFileName fn(FileNames::DataDir(), wxT("genres.txt"));
359 wxTextFile tf(fn.GetFullPath());
360
361 if (!tf.Exists() || !tf.Open()) {
363 return;
364 }
365
366 mGenres.clear();
367
368 int cnt = tf.GetLineCount();
369 for (int i = 0; i < cnt; i++) {
370 mGenres.push_back(tf.GetLine(i));
371 }
372}
373
374wxString Tags::GetUserGenre(int i)
375{
376 if (i >= 0 && i < GetNumUserGenres()) {
377 return mGenres[i];
378 }
379
380 return wxT("");
381}
382
383wxString Tags::GetGenre(int i)
384{
385 int cnt = WXSIZEOF(DefaultGenres);
386
387 if (i >= 0 && i < cnt) {
388 return DefaultGenres[i];
389 }
390
391 return wxT("");
392}
393
394int Tags::GetGenre(const wxString & name)
395{
396 int cnt = WXSIZEOF(DefaultGenres);
397
398 for (int i = 0; i < cnt; i++) {
399 if (name.CmpNoCase(DefaultGenres[i])) {
400 return i;
401 }
402 }
403
404 return 255;
405}
406
407bool Tags::HasTag(const wxString & name) const
408{
409 wxString key = name;
410 key.UpperCase();
411
412 auto iter = mXref.find(key);
413 return (iter != mXref.end());
414}
415
416wxString Tags::GetTag(const wxString & name) const
417{
418 wxString key = name;
419 key.UpperCase();
420
421 auto iter = mXref.find(key);
422
423 if (iter == mXref.end()) {
424 return wxEmptyString;
425 }
426
427 auto iter2 = mMap.find(iter->second);
428 if (iter2 == mMap.end()) {
429 wxASSERT(false);
430 return wxEmptyString;
431 }
432 else
433 return iter2->second;
434}
435
437{
438 return { mMap.begin(), mMap.end() };
439}
440
441void Tags::SetTag(const wxString & name, const wxString & value, const bool bSpecialTag)
442{
443 // We don't like empty names
444 if (name.empty()) {
445 return;
446 }
447
448 // Tag name must be ascii
449 if (!name.IsAscii()) {
450 wxLogError("Tag rejected (Non-ascii character in name)");
451 return;
452 }
453
454 // All keys are uppercase
455 wxString key = name;
456 key.UpperCase();
457
458 // Look it up
459 TagMap::iterator iter = mXref.find(key);
460
461 // The special tags, if empty, should not exist.
462 // However it is allowable for a custom tag to be empty.
463 // See Bug 440 and Bug 1382
464 if (value.empty() && bSpecialTag) {
465 // Erase the tag
466 if (iter == mXref.end())
467 // nothing to do
468 ;
469 else {
470 mMap.erase(iter->second);
471 mXref.erase(iter);
472 }
473 }
474 else
475 {
476 if (iter == mXref.end()) {
477 // Didn't find the tag
478
479 // Add a NEW tag
480 mXref[key] = name;
481 mMap[name] = value;
482 }
483 else if (iter->second != name) {
484 // Watch out for case differences!
485 mMap[name] = value;
486 mMap.erase(iter->second);
487 iter->second = name;
488 }
489 else {
490 // Update the value
491 mMap[iter->second] = value;
492 }
493 }
494}
495
496void Tags::SetTag(const wxString & name, const int & value)
497{
498 SetTag(name, wxString::Format(wxT("%d"), value));
499}
500
501bool Tags::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
502{
503 if (tag == "tags") {
504 return true;
505 }
506
507 if (tag == "tag") {
508 wxString n, v;
509
510 for (auto pair : attrs)
511 {
512 auto attr = pair.first;
513 auto value = pair.second;
514
515 if (attr == "name") {
516 n = value.ToWString();
517 }
518 else if (attr == "value") {
519 v = value.ToWString();
520 }
521 }
522
523 if (n == wxT("id3v2")) {
524 // LLL: This is obsolete, but it must be handled and ignored.
525 }
526 else {
527 SetTag(n, v);
528 }
529
530 return true;
531 }
532
533 return false;
534}
535
536XMLTagHandler *Tags::HandleXMLChild(const std::string_view& tag)
537{
538 if (tag == "tags") {
539 return this;
540 }
541
542 if (tag == "tag") {
543 return this;
544 }
545
546 return NULL;
547}
548
549void Tags::WriteXML(XMLWriter &xmlFile) const
550// may throw
551{
552 xmlFile.StartTag(wxT("tags"));
553
554 for (const auto &pair : GetRange()) {
555 const auto &n = pair.first;
556 const auto &v = pair.second;
557 xmlFile.StartTag(wxT("tag"));
558 xmlFile.WriteAttr(wxT("name"), n);
559 xmlFile.WriteAttr(wxT("value"), v);
560 xmlFile.EndTag(wxT("tag"));
561 }
562
563 xmlFile.EndTag(wxT("tags"));
564}
565
567[](const AudacityProject &project, XMLWriter &xmlFile){
568 Tags::Get(project).WriteXML(xmlFile);
569}
570};
571
572// Undo/redo handling
574 [](AudacityProject &project) -> std::shared_ptr<UndoStateExtension> {
575 return Tags::Get(project).shared_from_this();
576 }
577};
578
580{
581 // Restore tags
582 Tags::Set( project, shared_from_this() );
583}
wxT("CloseDown"))
const TranslatableString name
Definition: Distortion.cpp:76
FileConfig * gPrefs
Definition: Prefs.cpp:70
static ProjectFileIORegistry::ObjectWriterEntry entry
Definition: Tags.cpp:566
static ProjectFileIORegistry::ObjectReaderEntry readerEntry
Definition: Tags.cpp:205
static const AudacityProject::AttachedObjects::RegisteredFactory key
Definition: Tags.cpp:210
static UndoRedoExtensionRegistry::Entry sEntry
Definition: Tags.cpp:573
bool operator==(const Tags &lhs, const Tags &rhs)
Definition: Tags.cpp:332
static const wxChar * DefaultGenres[]
Definition: Tags.cpp:50
std::unordered_map< wxString, wxString > TagMap
Definition: Tags.h:56
#define TAG_ALBUM
Definition: Tags.h:60
#define TAG_TITLE
Definition: Tags.h:58
#define TAG_ARTIST
Definition: Tags.h:59
static const auto fn
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
virtual bool GetNextEntry(wxString &str, long &lIndex) const wxOVERRIDE
Definition: FileConfig.cpp:118
virtual const wxString & GetPath() const wxOVERRIDE
Definition: FileConfig.cpp:98
virtual void SetPath(const wxString &strPath) wxOVERRIDE
Definition: FileConfig.cpp:93
virtual bool GetFirstEntry(wxString &str, long &lIndex) const wxOVERRIDE
Definition: FileConfig.cpp:113
ID3 Tags (for MP3)
Definition: Tags.h:73
TagMap mXref
Definition: Tags.h:127
void Clear()
Definition: Tags.cpp:308
TagMap mMap
Definition: Tags.h:128
void WriteXML(XMLWriter &xmlFile) const
Definition: Tags.cpp:549
Iterators GetRange() const
Definition: Tags.cpp:436
bool HasTag(const wxString &name) const
Definition: Tags.cpp:407
wxString GetUserGenre(int value)
Definition: Tags.cpp:374
void Merge(const Tags &other)
Definition: Tags.cpp:246
void RestoreUndoRedoState(AudacityProject &) override
Modify the project when undoing or redoing to some state in history.
Definition: Tags.cpp:579
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
wxString GetGenre(int value)
Definition: Tags.cpp:383
std::shared_ptr< Tags > Duplicate() const
Definition: Tags.cpp:241
Tags()
Definition: Tags.cpp:231
virtual ~Tags()
Definition: Tags.cpp:237
void LoadDefaultGenres()
Definition: Tags.cpp:348
wxArrayString mGenres
Definition: Tags.h:130
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
Definition: Tags.cpp:501
Tags & operator=(const Tags &src)
Definition: Tags.cpp:253
static Tags & Set(AudacityProject &project, const std::shared_ptr< Tags > &tags)
Definition: Tags.cpp:224
int GetNumUserGenres()
Definition: Tags.cpp:343
bool IsEmpty()
Definition: Tags.cpp:297
void SetTag(const wxString &name, const wxString &value, const bool bSpecialTag=false)
Definition: Tags.cpp:441
void LoadGenres()
Definition: Tags.cpp:356
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
Definition: Tags.cpp:536
wxString GetTag(const wxString &name) const
Definition: Tags.cpp:416
void LoadDefaults()
Definition: Tags.cpp:266
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:42
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:25
FILES_API FilePath DataDir()
Audacity user data directory.
bool EqualMaps(const TagMap &map1, const TagMap &map2)
Definition: Tags.cpp:315
A convenience for use with range-for.
Definition: MemoryX.h:277
Typically statically constructed.
Definition: UndoManager.h:102
Typically statically constructed.