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