1 #include "track/taglib/trackmetadata_common.h"
2 
3 #include <taglib/tmap.h>
4 
5 #include "track/tracknumbers.h"
6 #include "util/assert.h"
7 #include "util/duration.h"
8 #include "util/logger.h"
9 
10 // TagLib has support for length in milliseconds since version 1.10
11 #define TAGLIB_HAS_LENGTH_IN_MILLISECONDS \
12     (TAGLIB_MAJOR_VERSION > 1) || ((TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION >= 10))
13 
14 namespace mixxx {
15 
16 namespace {
17 
18 Logger kLogger("TagLib");
19 
parseReplayGainGain(ReplayGain * pReplayGain,const QString & dbGain)20 bool parseReplayGainGain(
21         ReplayGain* pReplayGain,
22         const QString& dbGain) {
23     DEBUG_ASSERT(pReplayGain);
24 
25     bool isRatioValid = false;
26     double ratio = ReplayGain::ratioFromString(dbGain, &isRatioValid);
27     if (isRatioValid) {
28         // Some applications (e.g. Rapid Evolution 3) write a replay gain
29         // of 0 dB even if the replay gain is undefined. To be safe we
30         // ignore this special value and instead prefer to recalculate
31         // the replay gain.
32         if (ratio == ReplayGain::kRatio0dB) {
33             // special case
34             kLogger.info() << "Ignoring possibly undefined gain:" << dbGain;
35             ratio = ReplayGain::kRatioUndefined;
36         }
37         pReplayGain->setRatio(ratio);
38     }
39     return isRatioValid;
40 }
41 
parseReplayGainPeak(ReplayGain * pReplayGain,const QString & strPeak)42 bool parseReplayGainPeak(
43         ReplayGain* pReplayGain,
44         const QString& strPeak) {
45     DEBUG_ASSERT(pReplayGain);
46 
47     bool isPeakValid = false;
48     const CSAMPLE peak = ReplayGain::peakFromString(strPeak, &isPeakValid);
49     if (isPeakValid) {
50         pReplayGain->setPeak(peak);
51     }
52     return isPeakValid;
53 }
54 
55 } // anonymous namespace
56 
57 namespace taglib {
58 
toQString(const TagLib::String & tString)59 QString toQString(
60         const TagLib::String& tString) {
61     if (tString.isEmpty()) {
62         // TagLib::null/isNull() is deprecated so we cannot distinguish
63         // between null and empty strings.
64         return QString();
65     }
66     return TStringToQString(tString);
67 }
68 
toTString(const QString & qString)69 TagLib::String toTString(
70         const QString& qString) {
71     if (qString.isEmpty()) {
72         // TagLib::null/isNull() is deprecated so we cannot distinguish
73         // between null and empty strings.
74         return TagLib::String();
75     }
76     const QByteArray qba(qString.toUtf8());
77     return TagLib::String(qba.constData(), TagLib::String::UTF8);
78 }
79 
firstNonEmptyStringListItem(const TagLib::StringList & strList)80 TagLib::String firstNonEmptyStringListItem(
81         const TagLib::StringList& strList) {
82     for (const auto& str : strList) {
83         if (!str.isEmpty()) {
84             return str;
85         }
86     }
87     return TagLib::String();
88 }
89 
parseBpm(TrackMetadata * pTrackMetadata,const QString & sBpm)90 bool parseBpm(
91         TrackMetadata* pTrackMetadata,
92         const QString& sBpm) {
93     DEBUG_ASSERT(pTrackMetadata);
94     bool isBpmValid = false;
95     const double bpmValue = Bpm::valueFromString(sBpm, &isBpmValid);
96     if (isBpmValid) {
97         pTrackMetadata->refTrackInfo().setBpm(Bpm(bpmValue));
98     }
99     return isBpmValid;
100 }
101 
parseTrackGain(TrackMetadata * pTrackMetadata,const QString & dbGain)102 bool parseTrackGain(
103         TrackMetadata* pTrackMetadata,
104         const QString& dbGain) {
105     DEBUG_ASSERT(pTrackMetadata);
106 
107     ReplayGain replayGain(pTrackMetadata->getTrackInfo().getReplayGain());
108     bool isRatioValid = parseReplayGainGain(&replayGain, dbGain);
109     if (isRatioValid) {
110         pTrackMetadata->refTrackInfo().setReplayGain(replayGain);
111     }
112     return isRatioValid;
113 }
114 
parseTrackPeak(TrackMetadata * pTrackMetadata,const QString & strPeak)115 bool parseTrackPeak(
116         TrackMetadata* pTrackMetadata,
117         const QString& strPeak) {
118     DEBUG_ASSERT(pTrackMetadata);
119 
120     ReplayGain replayGain(pTrackMetadata->getTrackInfo().getReplayGain());
121     bool isPeakValid = parseReplayGainPeak(&replayGain, strPeak);
122     if (isPeakValid) {
123         pTrackMetadata->refTrackInfo().setReplayGain(replayGain);
124     }
125     return isPeakValid;
126 }
127 
128 #if defined(__EXTRA_METADATA__)
parseAlbumGain(TrackMetadata * pTrackMetadata,const QString & dbGain)129 bool parseAlbumGain(
130         TrackMetadata* pTrackMetadata,
131         const QString& dbGain) {
132     DEBUG_ASSERT(pTrackMetadata);
133 
134     ReplayGain replayGain(pTrackMetadata->getAlbumInfo().getReplayGain());
135     bool isRatioValid = parseReplayGainGain(&replayGain, dbGain);
136     if (isRatioValid) {
137         pTrackMetadata->refAlbumInfo().setReplayGain(replayGain);
138     }
139     return isRatioValid;
140 }
141 
parseAlbumPeak(TrackMetadata * pTrackMetadata,const QString & strPeak)142 bool parseAlbumPeak(
143         TrackMetadata* pTrackMetadata,
144         const QString& strPeak) {
145     DEBUG_ASSERT(pTrackMetadata);
146 
147     ReplayGain replayGain(pTrackMetadata->getAlbumInfo().getReplayGain());
148     bool isPeakValid = parseReplayGainPeak(&replayGain, strPeak);
149     if (isPeakValid) {
150         pTrackMetadata->refAlbumInfo().setReplayGain(replayGain);
151     }
152     return isPeakValid;
153 }
154 #endif // __EXTRA_METADATA__
155 
parseSeratoBeatGrid(TrackMetadata * pTrackMetadata,const QByteArray & data,FileType fileType)156 bool parseSeratoBeatGrid(
157         TrackMetadata* pTrackMetadata,
158         const QByteArray& data,
159         FileType fileType) {
160     DEBUG_ASSERT(pTrackMetadata);
161 
162     SeratoTags seratoTags(pTrackMetadata->getTrackInfo().getSeratoTags());
163     bool isValid = seratoTags.parseBeatGrid(data, fileType);
164     if (isValid) {
165         pTrackMetadata->refTrackInfo().setSeratoTags(seratoTags);
166     }
167     return isValid;
168 }
169 
parseSeratoBeatGrid(TrackMetadata * pTrackMetadata,const TagLib::String & data,FileType fileType)170 bool parseSeratoBeatGrid(
171         TrackMetadata* pTrackMetadata,
172         const TagLib::String& data,
173         FileType fileType) {
174     const TagLib::ByteVector byteVec =
175             data.data(TagLib::String::UTF8);
176     return parseSeratoBeatGrid(pTrackMetadata, toQByteArrayRaw(byteVec), fileType);
177 }
178 
parseSeratoMarkers(TrackMetadata * pTrackMetadata,const QByteArray & data,FileType fileType)179 bool parseSeratoMarkers(
180         TrackMetadata* pTrackMetadata,
181         const QByteArray& data,
182         FileType fileType) {
183     DEBUG_ASSERT(pTrackMetadata);
184 
185     SeratoTags seratoTags(pTrackMetadata->getTrackInfo().getSeratoTags());
186     bool isValid = seratoTags.parseMarkers(data, fileType);
187     if (isValid) {
188         pTrackMetadata->refTrackInfo().setSeratoTags(seratoTags);
189     }
190     return isValid;
191 }
192 
parseSeratoMarkers(TrackMetadata * pTrackMetadata,const TagLib::String & data,FileType fileType)193 bool parseSeratoMarkers(
194         TrackMetadata* pTrackMetadata,
195         const TagLib::String& data,
196         FileType fileType) {
197     const TagLib::ByteVector byteVec =
198             data.data(TagLib::String::UTF8);
199     return parseSeratoMarkers(pTrackMetadata, toQByteArrayRaw(byteVec), fileType);
200 }
201 
parseSeratoMarkers2(TrackMetadata * pTrackMetadata,const QByteArray & data,FileType fileType)202 bool parseSeratoMarkers2(
203         TrackMetadata* pTrackMetadata,
204         const QByteArray& data,
205         FileType fileType) {
206     DEBUG_ASSERT(pTrackMetadata);
207 
208     SeratoTags seratoTags(pTrackMetadata->getTrackInfo().getSeratoTags());
209     bool isValid = seratoTags.parseMarkers2(data, fileType);
210     if (isValid) {
211         pTrackMetadata->refTrackInfo().setSeratoTags(seratoTags);
212     }
213     return isValid;
214 }
215 
parseSeratoMarkers2(TrackMetadata * pTrackMetadata,const TagLib::String & data,FileType fileType)216 bool parseSeratoMarkers2(
217         TrackMetadata* pTrackMetadata,
218         const TagLib::String& data,
219         FileType fileType) {
220     const TagLib::ByteVector byteVec =
221             data.data(TagLib::String::UTF8);
222     return parseSeratoMarkers2(pTrackMetadata, toQByteArrayRaw(byteVec), fileType);
223 }
224 
dumpSeratoBeatGrid(const TrackMetadata & trackMetadata,FileType fileType)225 TagLib::String dumpSeratoBeatGrid(
226         const TrackMetadata& trackMetadata,
227         FileType fileType) {
228     const QByteArray seratoBeatGridData =
229             trackMetadata.getTrackInfo().getSeratoTags().dumpBeatGrid(fileType);
230     return TagLib::String(
231             seratoBeatGridData.constData(),
232             TagLib::String::UTF8);
233 }
234 
dumpSeratoMarkers(const TrackMetadata & trackMetadata,FileType fileType)235 TagLib::String dumpSeratoMarkers(
236         const TrackMetadata& trackMetadata,
237         FileType fileType) {
238     const QByteArray seratoMarkersData =
239             trackMetadata.getTrackInfo().getSeratoTags().dumpMarkers(fileType);
240     return TagLib::String(
241             seratoMarkersData.constData(),
242             TagLib::String::UTF8);
243 }
244 
dumpSeratoMarkers2(const TrackMetadata & trackMetadata,FileType fileType)245 TagLib::String dumpSeratoMarkers2(
246         const TrackMetadata& trackMetadata,
247         FileType fileType) {
248     const QByteArray seratoMarkers2Data =
249             trackMetadata.getTrackInfo().getSeratoTags().dumpMarkers2(fileType);
250     return TagLib::String(
251             seratoMarkers2Data.constData(),
252             TagLib::String::UTF8);
253 }
254 
importTrackMetadataFromTag(TrackMetadata * pTrackMetadata,const TagLib::Tag & tag,ReadTagMask readMask)255 void importTrackMetadataFromTag(
256         TrackMetadata* pTrackMetadata,
257         const TagLib::Tag& tag,
258         ReadTagMask readMask) {
259     if (!pTrackMetadata) {
260         return; // nothing to do
261     }
262 
263     pTrackMetadata->refTrackInfo().setTitle(toQString(tag.title()));
264     pTrackMetadata->refTrackInfo().setArtist(toQString(tag.artist()));
265     pTrackMetadata->refTrackInfo().setGenre(toQString(tag.genre()));
266     pTrackMetadata->refAlbumInfo().setTitle(toQString(tag.album()));
267 
268     if ((readMask & ReadTagFlag::OmitComment) == 0) {
269         pTrackMetadata->refTrackInfo().setComment(toQString(tag.comment()));
270     }
271 
272     int iYear = tag.year();
273     if (iYear > 0) {
274         pTrackMetadata->refTrackInfo().setYear(QString::number(iYear));
275     }
276 
277     int iTrack = tag.track();
278     if (iTrack > 0) {
279         pTrackMetadata->refTrackInfo().setTrackNumber(QString::number(iTrack));
280     }
281 }
282 
exportTrackMetadataIntoTag(TagLib::Tag * pTag,const TrackMetadata & trackMetadata,WriteTagMask writeMask)283 void exportTrackMetadataIntoTag(
284         TagLib::Tag* pTag,
285         const TrackMetadata& trackMetadata,
286         WriteTagMask writeMask) {
287     DEBUG_ASSERT(pTag); // already validated before
288 
289     pTag->setArtist(toTString(trackMetadata.getTrackInfo().getArtist()));
290     pTag->setTitle(toTString(trackMetadata.getTrackInfo().getTitle()));
291     pTag->setAlbum(toTString(trackMetadata.getAlbumInfo().getTitle()));
292     pTag->setGenre(toTString(trackMetadata.getTrackInfo().getGenre()));
293 
294     // Using setComment() from TagLib::Tag might have undesirable
295     // effects if the tag type supports multiple comment fields for
296     // different purposes, e.g. ID3v2. In this case setting the
297     // comment here should be omitted.
298     if (0 == (writeMask & WriteTagFlag::OmitComment)) {
299         pTag->setComment(toTString(trackMetadata.getTrackInfo().getComment()));
300     }
301 
302     // Specialized write functions for tags derived from Taglib::Tag might
303     // be able to write the complete string from trackMetadata.getTrackInfo().getYear()
304     // into the corresponding field. In this case parsing the year string
305     // here should be omitted.
306     if (0 == (writeMask & WriteTagFlag::OmitYear)) {
307         // Set the numeric year if available
308         const QDate yearDate(
309                 TrackMetadata::parseDateTime(trackMetadata.getTrackInfo().getYear()).date());
310         if (yearDate.isValid()) {
311             pTag->setYear(yearDate.year());
312         }
313     }
314 
315     // The numeric track number in TagLib::Tag does not reflect the total
316     // number of tracks! Specialized write functions for tags derived from
317     // Taglib::Tag might be able to handle both trackMetadata.getTrackInfo().getTrackNumber()
318     // and trackMetadata.getTrackInfo().getTrackTotal(). In this case parsing the track
319     // number string here is useless and should be omitted.
320     if (0 == (writeMask & WriteTagFlag::OmitTrackNumber)) {
321         // Set the numeric track number if available
322         TrackNumbers parsedTrackNumbers;
323         const TrackNumbers::ParseResult parseResult =
324                 TrackNumbers::parseFromString(trackMetadata.getTrackInfo().getTrackNumber(), &parsedTrackNumbers);
325         if (TrackNumbers::ParseResult::VALID == parseResult) {
326             pTag->setTrack(parsedTrackNumbers.getActual());
327         }
328     }
329 }
330 
331 } // namespace taglib
332 
333 } // namespace mixxx
334