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
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 value;
269 auto tagsGroup = gPrefs->BeginGroup("/Tags");
270 for(const auto& key : gPrefs->GetChildKeys())
271 {
272 gPrefs->Read(key, &value, {});
273 if(key == wxT("ID3V2")) {
274 // LLL: This is obsolute, but it must be handled and ignored.
275 }
276 else {
277 SetTag(key, value);
278 }
279 }
280}
281
283{
284 // At least one of these should be filled in, otherwise
285 // it's assumed that the tags have not been set...
287 return false;
288 }
289
290 return true;
291}
292
294{
295 mXref.clear();
296 mMap.clear();
297}
298
299size_t Tags::Count() const
300{
301 return mMap.size();
302}
303
304namespace {
305 bool EqualMaps(const TagMap &map1, const TagMap &map2)
306 {
307 // Maps are unordered, hash maps; can't just iterate in tandem and
308 // compare.
309 if (map1.size() != map2.size())
310 return false;
311
312 for (const auto &pair : map2) {
313 auto iter = map1.find(pair.first);
314 if (iter == map1.end() || iter->second != pair.second)
315 return false;
316 }
317
318 return true;
319 }
320}
321
322bool operator== (const Tags &lhs, const Tags &rhs)
323{
324 if (!EqualMaps(lhs.mXref, rhs.mXref))
325 return false;
326
327 if (!EqualMaps(lhs.mMap, rhs.mMap))
328 return false;
329
330 return lhs.mGenres == rhs.mGenres;
331}
332
334{
335 return mGenres.size();
336}
337
339{
340 mGenres.clear();
341 for (size_t i = 0; i < WXSIZEOF(DefaultGenres); i++) {
342 mGenres.push_back(DefaultGenres[i]);
343 }
344}
345
347{
348 wxFileName fn(FileNames::DataDir(), wxT("genres.txt"));
349 wxTextFile tf(fn.GetFullPath());
350
351 if (!tf.Exists() || !tf.Open()) {
353 return;
354 }
355
356 mGenres.clear();
357
358 int cnt = tf.GetLineCount();
359 for (int i = 0; i < cnt; i++) {
360 mGenres.push_back(tf.GetLine(i));
361 }
362}
363
364wxString Tags::GetUserGenre(int i)
365{
366 if (i >= 0 && i < GetNumUserGenres()) {
367 return mGenres[i];
368 }
369
370 return wxT("");
371}
372
373wxString Tags::GetGenre(int i)
374{
375 int cnt = WXSIZEOF(DefaultGenres);
376
377 if (i >= 0 && i < cnt) {
378 return DefaultGenres[i];
379 }
380
381 return wxT("");
382}
383
384int Tags::GetGenre(const wxString & name)
385{
386 int cnt = WXSIZEOF(DefaultGenres);
387
388 for (int i = 0; i < cnt; i++) {
389 if (name.CmpNoCase(DefaultGenres[i])) {
390 return i;
391 }
392 }
393
394 return 255;
395}
396
397bool Tags::HasTag(const wxString & name) const
398{
399 wxString key = name;
400 key.UpperCase();
401
402 auto iter = mXref.find(key);
403 return (iter != mXref.end());
404}
405
406wxString Tags::GetTag(const wxString & name) const
407{
408 wxString key = name;
409 key.UpperCase();
410
411 auto iter = mXref.find(key);
412
413 if (iter == mXref.end()) {
414 return wxEmptyString;
415 }
416
417 auto iter2 = mMap.find(iter->second);
418 if (iter2 == mMap.end()) {
419 wxASSERT(false);
420 return wxEmptyString;
421 }
422 else
423 return iter2->second;
424}
425
427{
428 return { mMap.begin(), mMap.end() };
429}
430
431void Tags::SetTag(const wxString & name, const wxString & value, const bool bSpecialTag)
432{
433 // We don't like empty names
434 if (name.empty()) {
435 return;
436 }
437
438 // Tag name must be ascii
439 if (!name.IsAscii()) {
440 wxLogError("Tag rejected (Non-ascii character in name)");
441 return;
442 }
443
444 // All keys are uppercase
445 wxString key = name;
446 key.UpperCase();
447
448 // Look it up
449 TagMap::iterator iter = mXref.find(key);
450
451 // The special tags, if empty, should not exist.
452 // However it is allowable for a custom tag to be empty.
453 // See Bug 440 and Bug 1382
454 if (value.empty() && bSpecialTag) {
455 // Erase the tag
456 if (iter == mXref.end())
457 // nothing to do
458 ;
459 else {
460 mMap.erase(iter->second);
461 mXref.erase(iter);
462 }
463 }
464 else
465 {
466 if (iter == mXref.end()) {
467 // Didn't find the tag
468
469 // Add a NEW tag
470 mXref[key] = name;
471 mMap[name] = value;
472 }
473 else if (iter->second != name) {
474 // Watch out for case differences!
475 mMap[name] = value;
476 mMap.erase(iter->second);
477 iter->second = name;
478 }
479 else {
480 // Update the value
481 mMap[iter->second] = value;
482 }
483 }
484}
485
486void Tags::SetTag(const wxString & name, const int & value)
487{
488 SetTag(name, wxString::Format(wxT("%d"), value));
489}
490
491bool Tags::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
492{
493 if (tag == "tags") {
494 return true;
495 }
496
497 if (tag == "tag") {
498 wxString n, v;
499
500 for (auto pair : attrs)
501 {
502 auto attr = pair.first;
503 auto value = pair.second;
504
505 if (attr == "name") {
506 n = value.ToWString();
507 }
508 else if (attr == "value") {
509 v = value.ToWString();
510 }
511 }
512
513 if (n == wxT("id3v2")) {
514 // LLL: This is obsolete, but it must be handled and ignored.
515 }
516 else {
517 SetTag(n, v);
518 }
519
520 return true;
521 }
522
523 return false;
524}
525
526XMLTagHandler *Tags::HandleXMLChild(const std::string_view& tag)
527{
528 if (tag == "tags") {
529 return this;
530 }
531
532 if (tag == "tag") {
533 return this;
534 }
535
536 return NULL;
537}
538
539void Tags::WriteXML(XMLWriter &xmlFile) const
540// may throw
541{
542 xmlFile.StartTag(wxT("tags"));
543
544 for (const auto &pair : GetRange()) {
545 const auto &n = pair.first;
546 const auto &v = pair.second;
547 xmlFile.StartTag(wxT("tag"));
548 xmlFile.WriteAttr(wxT("name"), n);
549 xmlFile.WriteAttr(wxT("value"), v);
550 xmlFile.EndTag(wxT("tag"));
551 }
552
553 xmlFile.EndTag(wxT("tags"));
554}
555
557[](const AudacityProject &project, XMLWriter &xmlFile){
558 Tags::Get(project).WriteXML(xmlFile);
559}
560};
561
562// Undo/redo handling
564 [](AudacityProject &project) -> std::shared_ptr<UndoStateExtension> {
565 return Tags::Get(project).shared_from_this();
566 }
567};
568
570{
571 // Restore tags
572 Tags::Set( project, shared_from_this() );
573}
wxT("CloseDown"))
const TranslatableString name
Definition: Distortion.cpp:76
audacity::BasicSettings * gPrefs
Definition: Prefs.cpp:68
static ProjectFileIORegistry::ObjectWriterEntry entry
Definition: Tags.cpp:556
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:563
bool operator==(const Tags &lhs, const Tags &rhs)
Definition: Tags.cpp:322
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
const auto project
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:275
ID3 Tags (for MP3)
Definition: Tags.h:73
TagMap mXref
Definition: Tags.h:129
void Clear()
Definition: Tags.cpp:293
TagMap mMap
Definition: Tags.h:130
void WriteXML(XMLWriter &xmlFile) const
Definition: Tags.cpp:539
Iterators GetRange() const
Definition: Tags.cpp:426
bool HasTag(const wxString &name) const
Definition: Tags.cpp:397
wxString GetUserGenre(int value)
Definition: Tags.cpp:364
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:569
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
wxString GetGenre(int value)
Definition: Tags.cpp:373
std::shared_ptr< Tags > Duplicate() const
Definition: Tags.cpp:241
size_t Count() const
Definition: Tags.cpp:299
Tags()
Definition: Tags.cpp:231
virtual ~Tags()
Definition: Tags.cpp:237
void LoadDefaultGenres()
Definition: Tags.cpp:338
wxArrayString mGenres
Definition: Tags.h:132
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
Definition: Tags.cpp:491
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:333
bool IsEmpty()
Definition: Tags.cpp:282
void SetTag(const wxString &name, const wxString &value, const bool bSpecialTag=false)
Definition: Tags.cpp:431
void LoadGenres()
Definition: Tags.cpp:346
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
Definition: Tags.cpp:526
wxString GetTag(const wxString &name) const
Definition: Tags.cpp:406
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
GroupScope BeginGroup(const wxString &prefix)
Appends a prefix to the current group or sets a new absolute path. Group that was set as current befo...
virtual wxArrayString GetChildKeys() const =0
Returns all child keys within the current group.
virtual bool Read(const wxString &key, bool *value) const =0
FILES_API FilePath DataDir()
Audacity user data directory.
bool EqualMaps(const TagMap &map1, const TagMap &map2)
Definition: Tags.cpp:305
A convenience for use with range-for.
Definition: IteratorX.h:39
Typically statically constructed.
Definition: UndoManager.h:102
Typically statically constructed.