Tag Parser: id3/id3v2tag.cpp Source File

Tag Parser 12.5.3 C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags

Loading...
Searching...
No Matches
id3v2tag.cpp
Go to the documentation of this file.
1#include "./id3v2tag.h"
2#include "./id3v2frameids.h"
3
4#include "../diagnostics.h"
5#include "../exceptions.h"
6#include "../mediafileinfo.h"
7
8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/conversion/stringconversion.h>
10
11#include <iostream>
12
13using namespace std;
14using namespace CppUtilities;
15
16namespace TagParser {
17
22
26 bool Id3v2Tag::supportsMultipleValues(KnownField field) const
27{
28 return supportsMultipleValues(fieldId(field));
29}
30
37 bool Id3v2Tag::supportsMultipleValues(IdentifierType id) const
38{
39 if (Id3v2FrameIds::isTextFrame(id)) {
40 return m_majorVersion > 3;
41 }
42 using namespace Id3v2FrameIds;
43 switch (id) {
44 case sUserDefinedText:
45 case lUserDefinedText:
46 case sUserDefinedURL:
47 case lUserDefinedURL:
48 case sRating:
49 case lRating:
50 case sComment:
51 case lComment:
52 case sCover:
53 case lCover:
54 case sUnsynchronizedLyrics:
55 case lUnsynchronizedLyrics:
56 case sSynchronizedLyrics:
57 case lSynchronizedLyrics:
58 case lPublisherWebpage:
59 return true;
60 default:
61 return false;
62 }
63}
64
65 void Id3v2Tag::ensureTextValuesAreProperlyEncoded()
66{
67 const auto encoding = proposedTextEncoding();
68 for (auto &field : fields()) {
69 auto &value = field.second.value();
70 value.convertDataEncoding(encoding);
71 value.convertDescriptionEncoding(encoding);
72 }
73}
74
78 void Id3v2Tag::internallyGetValuesFromField(const Id3v2Tag::FieldType &field, std::vector<const TagValue *> &values) const
79{
80 if (!field.value().isEmpty()) {
81 values.emplace_back(&field.value());
82 }
83 for (const auto &value : field.additionalValues()) {
84 if (!value.isEmpty()) {
85 values.emplace_back(&value);
86 }
87 }
88}
89
96 bool Id3v2Tag::internallySetValues(const IdentifierType &id, const std::vector<TagValue> &values)
97{
98 // use default implementation for non-text frames
99 if (!Id3v2FrameIds::isTextFrame(id)) {
100 return CRTPBase::internallySetValues(id, values);
101 }
102
103 // find existing text frame
104 auto range = fields().equal_range(id);
105 auto frameIterator = range.first;
106
107 // use existing frame or insert new text frame
108 auto valuesIterator = values.cbegin();
109 if (frameIterator != range.second) {
110 ++range.first;
111 // add primary value to existing frame
112 if (valuesIterator != values.cend()) {
113 frameIterator->second.setValue(*valuesIterator);
114 ++valuesIterator;
115 } else {
116 frameIterator->second.value().clearDataAndMetadata();
117 }
118 } else {
119 // skip if there is no existing frame but also no values to be assigned
120 if (valuesIterator == values.cend()) {
121 return true;
122 }
123 // add primary value to new frame
124 frameIterator = fields().insert(make_pair(id, Id3v2Frame(id, *valuesIterator)));
125 ++valuesIterator;
126 }
127
128 // add additional values to frame
129 frameIterator->second.additionalValues() = vector<TagValue>(valuesIterator, values.cend());
130
131 // remove remaining existing values (there are more existing values than specified ones)
132 for (; range.first != range.second; ++range.first) {
133 range.first->second.setValue(TagValue());
134 }
135 return true;
136}
137
138 Id3v2Tag::IdentifierType Id3v2Tag::internallyGetFieldId(KnownField field) const
139{
140 using namespace Id3v2FrameIds;
141 if (m_majorVersion >= 3) {
142 switch (field) {
143 case KnownField::Album:
144 return lAlbum;
145 case KnownField::Artist:
146 return lArtist;
147 case KnownField::Comment:
148 return lComment;
149 case KnownField::RecordDate:
150 return lRecordingTime; // (de)serializer converts to/from lYear/lRecordingDates/lDate/lTime
151 case KnownField::ReleaseDate:
152 return lReleaseTime;
153 case KnownField::Title:
154 return lTitle;
155 case KnownField::Genre:
156 return lGenre;
157 case KnownField::TrackPosition:
158 return lTrackPosition;
159 case KnownField::DiskPosition:
160 return lDiskPosition;
161 case KnownField::Encoder:
162 return lEncoder;
163 case KnownField::Bpm:
164 return lBpm;
165 case KnownField::Cover:
166 return lCover;
167 case KnownField::Lyricist:
168 return lWriter;
169 case KnownField::Length:
170 return lLength;
171 case KnownField::Language:
172 return lLanguage;
173 case KnownField::EncoderSettings:
174 return lEncoderSettings;
175 case KnownField::Lyrics:
176 return lUnsynchronizedLyrics;
177 case KnownField::SynchronizedLyrics:
178 return lSynchronizedLyrics;
179 case KnownField::Grouping:
180 return lContentGroupDescription;
181 case KnownField::Publisher:
182 return lRecordLabel;
183 case KnownField::Composer:
184 return lComposer;
185 case KnownField::PlayCounter:
186 return lPlayCounter;
187 case KnownField::Rating:
188 return lRating;
189 case KnownField::AlbumArtist:
190 return lAlbumArtist;
191 case KnownField::RemixedBy:
192 return lRemixedBy;
193 case KnownField::Copyright:
194 return lCopyright;
195 case KnownField::TaggingDate:
196 return lTaggingTime;
197 case KnownField::EncodingDate:
198 return lEncodingTime;
199 case KnownField::OriginalReleaseDate:
200 return lOriginalReleaseTime;
201 case KnownField::OriginalMediaType:
202 return lOriginalMediaType;
203 case KnownField::Mood:
204 return lMood;
205 case KnownField::InitialKey:
206 return lKey;
207 case KnownField::PublisherWebpage:
208 return lPublisherWebpage;
209 case KnownField::PerformerWebpage:
210 return lPerformerWebpage;
211 case KnownField::PurchaseItem:
212 return lPaymentWebpage;
213 case KnownField::Subtitle:
214 return lSubtitleOrDescriptionRefinement;
215 case KnownField::Synopsis:
216 return lLongDescription;
217 default:;
218 }
219 } else {
220 switch (field) {
221 case KnownField::Album:
222 return sAlbum;
223 case KnownField::Artist:
224 return sArtist;
225 case KnownField::Comment:
226 return sComment;
227 case KnownField::RecordDate:
228 return lRecordingTime; // (de)serializer converts to/from sYear/sRecordingDates/sDate/sTime
229 case KnownField::Title:
230 return sTitle;
231 case KnownField::Genre:
232 return sGenre;
233 case KnownField::TrackPosition:
234 return sTrackPosition;
235 case KnownField::DiskPosition:
236 return sDiskPosition;
237 case KnownField::Encoder:
238 return sEncoder;
239 case KnownField::Bpm:
240 return sBpm;
241 case KnownField::Cover:
242 return sCover;
243 case KnownField::Lyricist:
244 return sWriter;
245 case KnownField::Length:
246 return sLength;
247 case KnownField::Language:
248 return sLanguage;
249 case KnownField::EncoderSettings:
250 return sEncoderSettings;
251 case KnownField::Lyrics:
252 return sUnsynchronizedLyrics;
253 case KnownField::SynchronizedLyrics:
254 return sSynchronizedLyrics;
255 case KnownField::Grouping:
256 return sContentGroupDescription;
257 case KnownField::Publisher:
258 return sRecordLabel;
259 case KnownField::Composer:
260 return sComposer;
261 case KnownField::PlayCounter:
262 return sPlayCounter;
263 case KnownField::Rating:
264 return sRating;
265 case KnownField::AlbumArtist:
266 return sAlbumArtist;
267 case KnownField::RemixedBy:
268 return sRemixedBy;
269 case KnownField::Copyright:
270 return sCopyright;
271 case KnownField::OriginalMediaType:
272 return sOriginalMediaType;
273 case KnownField::InitialKey:
274 return sKey;
275 case KnownField::PublisherWebpage:
276 return sPublisherWebpage;
277 case KnownField::PerformerWebpage:
278 return sPerformerWebpage;
279 case KnownField::Subtitle:
280 return sSubtitleOrDescriptionRefinement;
281 default:;
282 }
283 }
284 return 0;
285}
286
287 KnownField Id3v2Tag::internallyGetKnownField(const IdentifierType &id) const
288{
289 using namespace Id3v2FrameIds;
290 switch (id) {
291 case lAlbum:
292 return KnownField::Album;
293 case lArtist:
294 return KnownField::Artist;
295 case lComment:
296 return KnownField::Comment;
297 case lRecordingTime:
298 case lYear:
299 return KnownField::RecordDate;
300 case lTitle:
301 return KnownField::Title;
302 case lGenre:
303 return KnownField::Genre;
304 case lTrackPosition:
305 return KnownField::TrackPosition;
306 case lDiskPosition:
307 return KnownField::DiskPosition;
308 case lEncoder:
309 return KnownField::Encoder;
310 case lBpm:
311 return KnownField::Bpm;
312 case lCover:
313 return KnownField::Cover;
314 case lWriter:
315 return KnownField::Lyricist;
316 case lLanguage:
317 return KnownField::Language;
318 case lLength:
319 return KnownField::Length;
320 case lEncoderSettings:
321 return KnownField::EncoderSettings;
322 case lUnsynchronizedLyrics:
323 return KnownField::Lyrics;
324 case lSynchronizedLyrics:
325 return KnownField::SynchronizedLyrics;
326 case lAlbumArtist:
327 return KnownField::AlbumArtist;
328 case lRemixedBy:
329 return KnownField::RemixedBy;
330 case lCopyright:
331 return KnownField::Copyright;
332 case lContentGroupDescription:
333 return KnownField::Grouping;
334 case lRecordLabel:
335 return KnownField::Publisher;
336 case lTaggingTime:
337 return KnownField::TaggingDate;
338 case lEncodingTime:
339 return KnownField::EncodingDate;
340 case lReleaseTime:
341 return KnownField::ReleaseDate;
342 case lOriginalReleaseTime:
343 return KnownField::OriginalReleaseDate;
344 case lOriginalMediaType:
345 return KnownField::OriginalMediaType;
346 case lMood:
347 return KnownField::Mood;
348 case lPlayCounter:
349 return KnownField::PlayCounter;
350 case lRating:
351 return KnownField::Rating;
352 case lISRC:
353 return KnownField::ISRC;
354 case lKey:
355 return KnownField::InitialKey;
356 case lComposer:
357 return KnownField::Composer;
358 case lPublisherWebpage:
359 return KnownField::PublisherWebpage;
360 case lPerformerWebpage:
361 return KnownField::PerformerWebpage;
362 case lPaymentWebpage:
363 return KnownField::PurchaseItem;
364 case lSubtitleOrDescriptionRefinement:
365 return KnownField::Subtitle;
366 case lLongDescription:
367 return KnownField::Synopsis;
368 case sAlbum:
369 return KnownField::Album;
370 case sArtist:
371 return KnownField::Artist;
372 case sComment:
373 return KnownField::Comment;
374 case sYear:
375 return KnownField::RecordDate;
376 case sTitle:
377 return KnownField::Title;
378 case sGenre:
379 return KnownField::Genre;
380 case sTrackPosition:
381 return KnownField::TrackPosition;
382 case sDiskPosition:
383 return KnownField::DiskPosition;
384 case sEncoder:
385 return KnownField::Encoder;
386 case sBpm:
387 return KnownField::Bpm;
388 case sCover:
389 return KnownField::Cover;
390 case sWriter:
391 return KnownField::Lyricist;
392 case sLanguage:
393 return KnownField::Language;
394 case sLength:
395 return KnownField::Length;
396 case sEncoderSettings:
397 return KnownField::EncoderSettings;
398 case sUnsynchronizedLyrics:
399 return KnownField::Lyrics;
400 case sSynchronizedLyrics:
401 return KnownField::SynchronizedLyrics;
402 case sAlbumArtist:
403 return KnownField::AlbumArtist;
404 case sRecordLabel:
405 return KnownField::Publisher;
406 case sRemixedBy:
407 return KnownField::RemixedBy;
408 case sCopyright:
409 return KnownField::Copyright;
410 case sPlayCounter:
411 return KnownField::PlayCounter;
412 case sRating:
413 return KnownField::Rating;
414 case sISRC:
415 return KnownField::ISRC;
416 case sOriginalMediaType:
417 return KnownField::OriginalMediaType;
418 case sKey:
419 return KnownField::InitialKey;
420 case sComposer:
421 return KnownField::Composer;
422 case sContentGroupDescription:
423 return KnownField::Grouping;
424 case sPublisherWebpage:
425 return KnownField::PublisherWebpage;
426 case sPerformerWebpage:
427 return KnownField::PerformerWebpage;
428 case sSubtitleOrDescriptionRefinement:
429 return KnownField::Subtitle;
430 default:
431 return KnownField::Invalid;
432 }
433}
434
435 TagDataType Id3v2Tag::internallyGetProposedDataType(const std::uint32_t &id) const
436{
437 using namespace Id3v2FrameIds;
438 switch (id) {
439 case lLength:
440 case sLength:
441 return TagDataType::TimeSpan;
442 case lBpm:
443 case sBpm:
444 case lYear:
445 case sYear:
446 case lPlayCounter:
447 case sPlayCounter:
448 return TagDataType::Integer;
449 case lTrackPosition:
450 case sTrackPosition:
451 case lDiskPosition:
452 return TagDataType::PositionInSet;
453 case lCover:
454 case sCover:
455 return TagDataType::Picture;
456 case lRating:
457 case sRating:
458 return TagDataType::Popularity;
459 default:
460 if (Id3v2FrameIds::isTextFrame(id)) {
461 return TagDataType::Text;
462 } else {
463 return TagDataType::Undefined;
464 }
465 }
466}
467
474void Id3v2Tag::convertOldRecordDateFields(const std::string &diagContext, Diagnostics &diag)
475{
476 // skip if it is a v2.4.0 tag and lRecordingTime is present
477 if (majorVersion() >= 4 && fields().find(Id3v2FrameIds::lRecordingTime) != fields().cend()) {
478 return;
479 }
480
481 // parse values of lYear/lRecordingDates/lDate/lTime/sYear/sRecordingDates/sDate/sTime fields
482 auto expr = DateTimeExpression();
483 auto year = 1, month = 1, day = 1, hour = 0, minute = 0;
484 if (const auto &v = value(Id3v2FrameIds::lYear)) {
485 expr.parts |= DateTimeParts::Year;
486 try {
487 year = v.toInteger();
488 } catch (const ConversionException &e) {
489 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse year from \"TYER\" frame: ", e.what()), diagContext);
490 }
491 }
492 if (const auto &v = value(Id3v2FrameIds::lDate)) {
493 expr.parts |= DateTimeParts::Day | DateTimeParts::Month;
494 try {
495 auto str = v.toString();
496 if (str.size() != 4) {
497 throw ConversionException("format is not DDMM");
498 }
499 day = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
500 month = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
501 } catch (const ConversionException &e) {
502 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse month and day from \"TDAT\" frame: ", e.what()), diagContext);
503 }
504 }
505 if (const auto &v = value(Id3v2FrameIds::lTime)) {
506 expr.parts |= DateTimeParts::Hour | DateTimeParts::Minute;
507 try {
508 auto str = v.toString();
509 if (str.size() != 4) {
510 throw ConversionException("format is not HHMM");
511 }
512 hour = stringToNumber<unsigned short>(std::string_view(str.data() + 0, 2));
513 minute = stringToNumber<unsigned short>(std::string_view(str.data() + 2, 2));
514 } catch (const ConversionException &e) {
515 diag.emplace_back(DiagLevel::Critical, argsToString("Unable to parse hour and minute from \"TIME\" frame: ", +e.what()), diagContext);
516 }
517 }
518
519 // set the field values as DateTime
520 if (expr.parts == DateTimeParts::None) {
521 return;
522 }
523 try {
524 expr.value = DateTime::fromDateAndTime(year, month, day, hour, minute);
525 setValue(Id3v2FrameIds::lRecordingTime, TagValue(expr));
526 } catch (const ConversionException &e) {
527 try {
528 // try to set at least the year
529 expr.parts = DateTimeParts::Year;
530 expr.value = DateTime::fromDate(year);
531 setValue(Id3v2FrameIds::lRecordingTime, TagValue(expr));
532 diag.emplace_back(DiagLevel::Critical,
533 argsToString(
534 "Unable to parse the full date of the recording. Only the 'Year' frame could be parsed; related frames failed: ", e.what()),
535 diagContext);
536 } catch (const ConversionException &) {
537 }
538 diag.emplace_back(
539 DiagLevel::Critical, argsToString("Unable to parse a valid date from the 'Year' frame and related frames: ", e.what()), diagContext);
540 }
541}
542
550 void Id3v2Tag::parse(istream &stream, const std::uint64_t maximalSize, Diagnostics &diag)
551{
552 // prepare parsing
553 static const string context("parsing ID3v2 tag");
554 BinaryReader reader(&stream);
555 const auto startOffset = static_cast<std::uint64_t>(stream.tellg());
556
557 // check whether the header is truncated
558 if (maximalSize && maximalSize < 10) {
559 diag.emplace_back(DiagLevel::Critical, "ID3v2 header is truncated (at least 10 bytes expected).", context);
560 throw TruncatedDataException();
561 }
562
563 // read signature: ID3
564 if (reader.readUInt24BE() != 0x494433u) {
565 diag.emplace_back(DiagLevel::Critical, "Signature is invalid.", context);
566 throw InvalidDataException();
567 }
568 // read header data
569 const std::uint8_t majorVersion = reader.readByte();
570 const std::uint8_t revisionVersion = reader.readByte();
571 setVersion(majorVersion, revisionVersion);
572 m_flags = reader.readByte();
573 m_sizeExcludingHeader = reader.readSynchsafeUInt32BE();
574 m_size = 10 + m_sizeExcludingHeader;
575 if (m_sizeExcludingHeader == 0) {
576 diag.emplace_back(DiagLevel::Warning, "ID3v2 tag seems to be empty.", context);
577 return;
578 }
579
580 // check if the version
581 if (!isVersionSupported()) {
582 diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag couldn't be parsed, because its version is not supported.", context);
583 throw VersionNotSupportedException();
584 }
585
586 // read extended header (if present)
587 if (hasExtendedHeader()) {
588 if (maximalSize && maximalSize < 14) {
589 diag.emplace_back(DiagLevel::Critical, "Extended header denoted but not present.", context);
590 throw TruncatedDataException();
591 }
592 m_extendedHeaderSize = reader.readSynchsafeUInt32BE();
593 if (m_extendedHeaderSize < 6 || m_extendedHeaderSize > m_sizeExcludingHeader || (maximalSize && maximalSize < (10 + m_extendedHeaderSize))) {
594 diag.emplace_back(DiagLevel::Critical, "Extended header is invalid/truncated.", context);
595 throw TruncatedDataException();
596 }
597 stream.seekg(m_extendedHeaderSize - 4, ios_base::cur);
598 }
599
600 // how many bytes remain for frames and padding?
601 std::uint32_t bytesRemaining = m_sizeExcludingHeader - m_extendedHeaderSize;
602 if (maximalSize && bytesRemaining > maximalSize) {
603 bytesRemaining = static_cast<std::uint32_t>(maximalSize);
604 diag.emplace_back(DiagLevel::Critical, "Frames are truncated.", context);
605 }
606
607 // read frames
608 auto pos = static_cast<std::uint64_t>(stream.tellg());
609 while (bytesRemaining) {
610 // seek to next frame
611 stream.seekg(static_cast<streamoff>(pos));
612 // parse frame
613 Id3v2Frame frame;
614 try {
615 frame.parse(reader, majorVersion, bytesRemaining, diag);
616 if (Id3v2FrameIds::isTextFrame(frame.id()) && fields().count(frame.id()) == 1) {
617 diag.emplace_back(DiagLevel::Warning, "The text frame " % frame.idToString() + " exists more than once.", context);
618 }
619 fields().emplace(frame.id(), std::move(frame));
620 } catch (const NoDataFoundException &) {
621 if (frame.hasPaddingReached()) {
622 m_paddingSize = startOffset + m_size - pos;
623 break;
624 }
625 } catch (const Failure &) {
626 }
627
628 // calculate next frame offset
629 if (frame.totalSize() <= bytesRemaining) {
630 pos += frame.totalSize();
631 bytesRemaining -= frame.totalSize();
632 } else {
633 pos += bytesRemaining;
634 bytesRemaining = 0;
635 }
636 }
637
638 if (m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
639 convertOldRecordDateFields(context, diag);
640 }
641
642 // check for extended header
643 if (!hasFooter()) {
644 return;
645 }
646 if (maximalSize && m_size + 10 < maximalSize) {
647 // the footer does not provide additional information, just check the signature
648 stream.seekg(static_cast<streamoff>(startOffset + (m_size += 10)));
649 if (reader.readUInt24LE() != 0x494433u) {
650 diag.emplace_back(DiagLevel::Critical, "Footer signature is invalid.", context);
651 }
652 // skip remaining footer
653 stream.seekg(7, ios_base::cur);
654 } else {
655 diag.emplace_back(DiagLevel::Critical, "Footer denoted but not present.", context);
656 throw TruncatedDataException();
657 }
658}
659
670 Id3v2TagMaker Id3v2Tag::prepareMaking(Diagnostics &diag)
671{
672 return Id3v2TagMaker(*this, diag);
673}
674
682 void Id3v2Tag::make(ostream &stream, std::uint32_t padding, Diagnostics &diag)
683{
684 prepareMaking(diag).make(stream, padding, diag);
685}
686
691 void Id3v2Tag::setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
692{
693 m_majorVersion = majorVersion;
694 m_revisionVersion = revisionVersion;
695 m_version = argsToString('2', '.', majorVersion, '.', revisionVersion);
696}
697
704
710 bool FrameComparer::operator()(std::uint32_t lhs, std::uint32_t rhs) const
711{
712 if (lhs == rhs) {
713 return false;
714 }
715
716 const bool lhsLong = Id3v2FrameIds::isLongId(lhs);
717 const bool rhsLong = Id3v2FrameIds::isLongId(rhs);
718 if (lhsLong != rhsLong) {
719 if (!lhsLong) {
720 lhs = Id3v2FrameIds::convertToLongId(lhs);
721 if (!lhs) {
722 return true;
723 }
724 } else if (!rhsLong) {
725 rhs = Id3v2FrameIds::convertToLongId(rhs);
726 if (!rhs) {
727 return true;
728 }
729 }
730 }
731
732 if (lhs == Id3v2FrameIds::lUniqueFileId || lhs == Id3v2FrameIds::sUniqueFileId) {
733 return true;
734 }
735 if (rhs == Id3v2FrameIds::lUniqueFileId || rhs == Id3v2FrameIds::sUniqueFileId) {
736 return false;
737 }
738 if (lhs == Id3v2FrameIds::lTitle || lhs == Id3v2FrameIds::sTitle) {
739 return true;
740 }
741 if (rhs == Id3v2FrameIds::lTitle || rhs == Id3v2FrameIds::sTitle) {
742 return false;
743 }
744
745 const bool lhstextfield = Id3v2FrameIds::isTextFrame(lhs);
746 const bool rhstextfield = Id3v2FrameIds::isTextFrame(rhs);
747 if (lhstextfield && !rhstextfield) {
748 return true;
749 }
750 if (!lhstextfield && rhstextfield) {
751 return false;
752 }
753
754 if (lhs == Id3v2FrameIds::lCover || lhs == Id3v2FrameIds::sCover) {
755 return false;
756 }
757 if (rhs == Id3v2FrameIds::lCover || rhs == Id3v2FrameIds::sCover) {
758 return true;
759 }
760 return lhs < rhs;
761}
762
769
773void Id3v2Tag::removeOldRecordDateRelatedFields()
774{
775 for (auto field : { Id3v2FrameIds::lYear, Id3v2FrameIds::lRecordingDates, Id3v2FrameIds::lDate, Id3v2FrameIds::lTime }) {
776 fields().erase(field);
777 }
778}
779
783void Id3v2Tag::prepareRecordDataForMaking(const std::string &diagContext, Diagnostics &diag)
784{
785 // get rid of lYear/lRecordingDates/lDate/lTime/sYear/sRecordingDates/sDate/sTime if writing v2.4.0 or newer
786 // note: If the tag was initially v2.3.0 or older the "old" fields have already been converted to lRecordingTime when
787 // parsing and the generic accessors propose using lRecordingTime in any case.
788 if (majorVersion() >= 4) {
789 removeOldRecordDateRelatedFields();
790 return;
791 }
792
793 // convert lRecordingTime to old fields for v2.3.0 and older
794 const auto recordingTimeFieldIterator = fields().find(Id3v2FrameIds::lRecordingTime);
795 // -> If the auto-created lRecordingTime field (see note above) has been completely removed write the old fields as-is.
796 // This allows one to bypass this handling and set the old fields explicitly.
797 if (recordingTimeFieldIterator == fields().cend()) {
798 return;
799 }
800 // -> simply remove all old fields if lRecordingTime is set to an empty value
801 const auto &recordingTime = recordingTimeFieldIterator->second.value();
802 if (recordingTime.isEmpty()) {
803 removeOldRecordDateRelatedFields();
804 return;
805 }
806 // -> convert lRecordingTime (which is supposed to be an ISO string) to a DateTime
807 try {
808 const auto dateTimeExpr = recordingTime.toDateTimeExpression();
809 const auto &asDateTime = dateTimeExpr.value;
810 // -> remove any existing old fields to avoid any leftovers
811 removeOldRecordDateRelatedFields();
812 // -> assign old fields from parsed DateTime
813 std::stringstream year, date, time;
814 if (dateTimeExpr.parts & DateTimeParts::Year) {
815 year << std::setfill('0') << std::setw(4) << asDateTime.year();
816 setValue(Id3v2FrameIds::lYear, TagValue(year.str()));
817 }
818 if (dateTimeExpr.parts & (DateTimeParts::Day | DateTimeParts::Month)) {
819 date << std::setfill('0') << std::setw(2) << asDateTime.day() << std::setfill('0') << std::setw(2) << asDateTime.month();
820 setValue(Id3v2FrameIds::lDate, TagValue(date.str()));
821 }
822 if (dateTimeExpr.parts & DateTimeParts::Time) {
823 time << std::setfill('0') << std::setw(2) << asDateTime.hour() << std::setfill('0') << std::setw(2) << asDateTime.minute();
824 setValue(Id3v2FrameIds::lTime, TagValue(time.str()));
825 }
826 if (dateTimeExpr.parts & (DateTimeParts::Second | DateTimeParts::SubSecond)) {
827 diag.emplace_back(DiagLevel::Warning,
828 "The recording time field (TDRC) has been truncated to full minutes when converting to corresponding fields for older ID3v2 "
829 "versions.",
830 diagContext);
831 }
832 } catch (const ConversionException &e) {
833 try {
834 diag.emplace_back(DiagLevel::Critical,
835 argsToString("Unable to convert recording time field (TDRC) with the value \"", recordingTime.toString(),
836 "\" to corresponding fields for older ID3v2 versions: ", e.what()),
837 diagContext);
838 } catch (const ConversionException &) {
839 diag.emplace_back(DiagLevel::Critical,
840 argsToString("Unable to convert recording time field (TRDA) to corresponding fields for older ID3v2 versions: ", e.what()),
841 diagContext);
842 }
843 }
844 // -> get rid of lRecordingTime
845 fields().erase(Id3v2FrameIds::lRecordingTime);
846}
847
852Id3v2TagMaker::Id3v2TagMaker(Id3v2Tag &tag, Diagnostics &diag)
853 : m_tag(tag)
854 , m_framesSize(0)
855{
856 static const string context("making ID3v2 tag");
857
858 // check if version is supported
859 // (the version could have been changed using setVersion())
860 if (!tag.isVersionSupported()) {
861 diag.emplace_back(DiagLevel::Critical, "The ID3v2 tag version isn't supported.", context);
862 throw VersionNotSupportedException();
863 }
864
865 if (m_tag.m_handlingFlags & Id3v2HandlingFlags::ConvertRecordDateFields) {
866 tag.prepareRecordDataForMaking(context, diag);
867 }
868
869 // prepare frames
870 m_maker.reserve(tag.fields().size());
871 for (auto &pair : tag.fields()) {
872 try {
873 m_maker.emplace_back(pair.second.prepareMaking(tag.majorVersion(), diag));
874 m_framesSize += m_maker.back().requiredSize();
875 } catch (const Failure &) {
876 }
877 }
878
879 // calculate required size
880 // -> header + size of frames
881 m_requiredSize = 10 + m_framesSize;
882}
883
891 void Id3v2TagMaker::make(std::ostream &stream, std::uint32_t padding, Diagnostics &diag)
892{
893 CPP_UTILITIES_UNUSED(diag)
894
895 BinaryWriter writer(&stream);
896
897 // write header
898 // -> signature
899 writer.writeUInt24BE(0x494433u);
900 // -> version
901 writer.writeByte(m_tag.majorVersion());
902 writer.writeByte(m_tag.revisionVersion());
903 // -> flags, but without extended header or compression bit set
904 writer.writeByte(m_tag.flags() & 0xBF);
905 // -> size (excluding header)
906 writer.writeSynchsafeUInt32BE(m_framesSize + padding);
907
908 // write frames
909 for (auto &maker : m_maker) {
910 maker.make(writer);
911 }
912
913 MediaFileInfo::writePadding(stream, padding);
914}
915
916} // namespace TagParser
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition diagnostics.h:156
TagParser::Failure
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition exceptions.h:11
TagParser::FieldMapBasedTag< Id3v2Tag >::setValue
bool setValue(const IdentifierType &id, const TagValue &value)
Definition fieldbasedtag.h:253
TagParser::FieldMapBasedTag< Id3v2Tag >::IdentifierType
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType::IdentifierType IdentifierType
Definition fieldbasedtag.h:35
TagParser::FieldMapBasedTag< Id3v2Tag >::value
const TagValue & value(const IdentifierType &id) const
Definition fieldbasedtag.h:161
TagParser::FieldMapBasedTag< Id3v2Tag >::fields
const std::multimap< IdentifierType, FieldType, Compare > & fields() const
Definition fieldbasedtag.h:327
TagParser::FieldMapBasedTag< Id3v2Tag >::internallySetValues
bool internallySetValues(const IdentifierType &id, const std::vector< TagValue > &values)
Definition fieldbasedtag.h:222
TagParser::FieldMapBasedTag< Id3v2Tag >::FieldType
typename FieldMapBasedTagTraits< Id3v2Tag >::FieldType FieldType
Definition fieldbasedtag.h:34
TagParser::FieldMapBasedTag< Id3v2Tag >::values
std::vector< const TagValue * > values(const IdentifierType &id) const
Definition fieldbasedtag.h:175
TagParser::FieldMapBasedTag< Id3v2Tag >::fieldId
IdentifierType fieldId(KnownField value) const
Definition fieldbasedtag.h:356
TagParser::Id3v2Frame
The Id3v2Frame class is used by Id3v2Tag to store the fields.
Definition id3v2frame.h:86
TagParser::Id3v2Frame::parse
void parse(CppUtilities::BinaryReader &reader, std::uint32_t version, std::uint32_t maximalSize, Diagnostics &diag)
Parses a frame from the stream read using the specified reader.
Definition id3v2frame.cpp:151
TagParser::Id3v2Frame::totalSize
std::uint32_t totalSize() const
Returns the total size of the frame in bytes.
Definition id3v2frame.h:221
TagParser::Id3v2Frame::hasPaddingReached
bool hasPaddingReached() const
Returns whether the padding has reached.
Definition id3v2frame.h:197
TagParser::Id3v2TagMaker::make
void make(std::ostream &stream, std::uint32_t padding, Diagnostics &diag)
Saves the tag (specified when constructing the object) to the specified stream.
Definition id3v2tag.cpp:891
TagParser::Id3v2Tag
Implementation of TagParser::Tag for ID3v2 tags.
Definition id3v2tag.h:78
TagParser::Id3v2Tag::revisionVersion
std::uint8_t revisionVersion() const
Returns the revision version if known; otherwise returns 0.
Definition id3v2tag.h:205
TagParser::Id3v2Tag::make
void make(std::ostream &targetStream, std::uint32_t padding, Diagnostics &diag)
Writes tag information to the specified stream.
Definition id3v2tag.cpp:682
TagParser::Id3v2Tag::hasExtendedHeader
bool hasExtendedHeader() const
Returns an indication whether an extended header is used.
Definition id3v2tag.h:240
TagParser::Id3v2Tag::isVersionSupported
bool isVersionSupported() const
Returns an indication whether the version is supported by the Id3v2Tag class.
Definition id3v2tag.h:216
TagParser::Id3v2Tag::supportsMultipleValues
bool supportsMultipleValues(KnownField field) const override
Allows multiple values for some fields.
Definition id3v2tag.cpp:26
TagParser::Id3v2Tag::setVersion
void setVersion(std::uint8_t majorVersion, std::uint8_t revisionVersion)
Sets the version to the specified majorVersion and the specified revisionVersion.
Definition id3v2tag.cpp:691
TagParser::Id3v2Tag::internallyGetKnownField
KnownField internallyGetKnownField(const IdentifierType &id) const
Definition id3v2tag.cpp:287
TagParser::Id3v2Tag::majorVersion
std::uint8_t majorVersion() const
Returns the major version if known; otherwise returns 0.
Definition id3v2tag.h:197
TagParser::Id3v2Tag::hasFooter
bool hasFooter() const
Returns an indication whether a footer is present.
Definition id3v2tag.h:256
TagParser::Id3v2Tag::internallyGetValuesFromField
void internallyGetValuesFromField(const FieldType &field, std::vector< const TagValue * > &values) const
Adds additional values as well.
Definition id3v2tag.cpp:78
TagParser::Id3v2Tag::internallySetValues
bool internallySetValues(const IdentifierType &id, const std::vector< TagValue > &values)
Uses default implementation for non-text frames and applies special handling to text frames.
Definition id3v2tag.cpp:96
TagParser::Id3v2Tag::proposedTextEncoding
TagTextEncoding proposedTextEncoding() const override
Returns the proposed text encoding.
Definition id3v2tag.h:150
TagParser::Id3v2Tag::internallyGetProposedDataType
TagDataType internallyGetProposedDataType(const std::uint32_t &id) const
Definition id3v2tag.cpp:435
TagParser::Id3v2Tag::prepareMaking
Id3v2TagMaker prepareMaking(Diagnostics &diag)
Prepares making.
Definition id3v2tag.cpp:670
TagParser::Id3v2Tag::ensureTextValuesAreProperlyEncoded
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
Definition id3v2tag.cpp:65
TagParser::Id3v2Tag::internallyGetFieldId
IdentifierType internallyGetFieldId(KnownField field) const
Definition id3v2tag.cpp:138
TagParser::Id3v2Tag::Id3v2TagMaker
friend class Id3v2TagMaker
Definition id3v2tag.h:80
TagParser::Id3v2Tag::parse
void parse(std::istream &sourceStream, const std::uint64_t maximalSize, Diagnostics &diag)
Parses tag information from the specified stream.
Definition id3v2tag.cpp:550
TagParser::InvalidDataException
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition exceptions.h:25
TagParser::MediaFileInfo::writePadding
static void writePadding(std::ostream &outputStream, uint64_t size)
Writes the specified number of zeroes to outputStream.
Definition mediafileinfo.cpp:1399
TagParser::NoDataFoundException
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition exceptions.h:18
TagParser::TagField::id
IdentifierType & id()
Returns the id of the current TagField.
Definition generictagfield.h:116
TagParser::TagField::idToString
std::string idToString() const
Returns the id of the current TagField as string.
Definition generictagfield.h:132
TagParser::TagValue
The TagValue class wraps values of different types.
Definition tagvalue.h:147
TagParser::Tag::m_version
std::string m_version
Definition tag.h:216
TagParser::Tag::m_size
std::uint64_t m_size
Definition tag.h:217
TagParser::TruncatedDataException
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition exceptions.h:39
TagParser::VersionNotSupportedException
The exception that is thrown when an operation fails because the detected or specified version is not...
Definition exceptions.h:53
diagnostics.h
exceptions.h
id3v2frameids.h
id3v2tag.h
mediafileinfo.h
CppUtilities
Definition abstractcontainer.h:16
TagParser::Id3v2FrameIds
Encapsulates the most common ID3v2 frame IDs and related functions.
Definition id3v2frameids.h:12
TagParser::Id3v2FrameIds::isLongId
constexpr bool isLongId(std::uint32_t id)
Returns an indication whether the specified id is a long frame id.
Definition id3v2frameids.h:118
TagParser::Id3v2FrameIds::lYear
@ lYear
Definition id3v2frameids.h:17
TagParser::Id3v2FrameIds::lDate
@ lDate
Definition id3v2frameids.h:20
TagParser::Id3v2FrameIds::lTime
@ lTime
Definition id3v2frameids.h:21
TagParser::Id3v2FrameIds::lCover
@ lCover
Definition id3v2frameids.h:32
TagParser::Id3v2FrameIds::sUniqueFileId
@ sUniqueFileId
Definition id3v2frameids.h:92
TagParser::Id3v2FrameIds::lRecordingTime
@ lRecordingTime
Definition id3v2frameids.h:22
TagParser::Id3v2FrameIds::lRecordingDates
@ lRecordingDates
Definition id3v2frameids.h:19
TagParser::Id3v2FrameIds::lUniqueFileId
@ lUniqueFileId
Definition id3v2frameids.h:45
TagParser::Id3v2FrameIds::sTitle
@ sTitle
Definition id3v2frameids.h:89
TagParser::Id3v2FrameIds::lTitle
@ lTitle
Definition id3v2frameids.h:41
TagParser::Id3v2FrameIds::sCover
@ sCover
Definition id3v2frameids.h:80
TagParser::Id3v2FrameIds::isTextFrame
constexpr bool isTextFrame(std::uint32_t id)
Returns an indication whether the specified id is a text frame id.
Definition id3v2frameids.h:134
TagParser::Id3v2FrameIds::convertToLongId
TAG_PARSER_EXPORT std::uint32_t convertToLongId(std::uint32_t id)
Converts the specified short frame ID to the equivalent long frame ID.
Definition id3v2frameids.cpp:117
TagParser::VorbisCommentIds::year
constexpr TAG_PARSER_EXPORT std::string_view year()
Definition vorbiscommentids.h:177
TagParser::VorbisCommentIds::date
constexpr TAG_PARSER_EXPORT std::string_view date()
Definition vorbiscommentids.h:173
TagParser
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
TagParser::KnownField
KnownField
Specifies the field.
Definition tag.h:37
TagParser::KnownField::EncodingDate
@ EncodingDate
Definition tag.h:106
TagParser::KnownField::Subtitle
@ Subtitle
Definition tag.h:68
TagParser::KnownField::PlayCounter
@ PlayCounter
Definition tag.h:115
TagParser::KnownField::Grouping
@ Grouping
Definition tag.h:59
TagParser::KnownField::Comment
@ Comment
Definition tag.h:43
TagParser::KnownField::Lyricist
@ Lyricist
Definition tag.h:46
TagParser::KnownField::Synopsis
@ Synopsis
Definition tag.h:102
TagParser::KnownField::Mood
@ Mood
Definition tag.h:96
TagParser::KnownField::TrackPosition
@ TrackPosition
Definition tag.h:47
TagParser::KnownField::Publisher
@ Publisher
Definition tag.h:95
TagParser::KnownField::Artist
@ Artist
Definition tag.h:41
TagParser::KnownField::EncoderSettings
@ EncoderSettings
Definition tag.h:56
TagParser::KnownField::Language
@ Language
Definition tag.h:55
TagParser::KnownField::Invalid
@ Invalid
Definition tag.h:38
TagParser::KnownField::Copyright
@ Copyright
Definition tag.h:133
TagParser::KnownField::TaggingDate
@ TaggingDate
Definition tag.h:107
TagParser::KnownField::SynchronizedLyrics
@ SynchronizedLyrics
Definition tag.h:58
TagParser::KnownField::PurchaseItem
@ PurchaseItem
Definition tag.h:128
TagParser::KnownField::InitialKey
@ InitialKey
Definition tag.h:103
TagParser::KnownField::ReleaseDate
@ ReleaseDate
Definition tag.h:67
TagParser::KnownField::OriginalMediaType
@ OriginalMediaType
Definition tag.h:97
TagParser::KnownField::Encoder
@ Encoder
Definition tag.h:51
TagParser::KnownField::Genre
@ Genre
Definition tag.h:42
TagParser::KnownField::AlbumArtist
@ AlbumArtist
Definition tag.h:66
TagParser::KnownField::OriginalReleaseDate
@ OriginalReleaseDate
Definition tag.h:108
TagParser::KnownField::Lyrics
@ Lyrics
Definition tag.h:57
TagParser::KnownField::Bpm
@ Bpm
Definition tag.h:44
TagParser::KnownField::ISRC
@ ISRC
Definition tag.h:118
TagParser::KnownField::Title
@ Title
Definition tag.h:39
TagParser::KnownField::Length
@ Length
Definition tag.h:54
TagParser::KnownField::Composer
@ Composer
Definition tag.h:62
TagParser::KnownField::Album
@ Album
Definition tag.h:40
TagParser::KnownField::Cover
@ Cover
Definition tag.h:61
TagParser::KnownField::RecordDate
@ RecordDate
Definition tag.h:52
TagParser::KnownField::Rating
@ Rating
Definition tag.h:63
TagParser::KnownField::PerformerWebpage
@ PerformerWebpage
Definition tag.h:140
TagParser::KnownField::RemixedBy
@ RemixedBy
Definition tag.h:92
TagParser::KnownField::PublisherWebpage
@ PublisherWebpage
Definition tag.h:137
TagParser::KnownField::DiskPosition
@ DiskPosition
Definition tag.h:48
TagParser::DiagLevel::Warning
@ Warning
Definition diagnostics.h:20
TagParser::DiagLevel::Critical
@ Critical
Definition diagnostics.h:21
TagParser::Id3v2HandlingFlags::ConvertRecordDateFields
@ ConvertRecordDateFields
Definition id3v2tag.h:19
TagParser::TagDataType
TagDataType
Specifies the data type.
Definition tagvalue.h:119
TagParser::TagDataType::TimeSpan
@ TimeSpan
Definition tagvalue.h:124
TagParser::TagDataType::DateTimeExpression
@ DateTimeExpression
Definition tagvalue.h:131
TagParser::TagDataType::PositionInSet
@ PositionInSet
Definition tagvalue.h:122
TagParser::TagDataType::Picture
@ Picture
Definition tagvalue.h:126
TagParser::TagDataType::Text
@ Text
Definition tagvalue.h:120
TagParser::TagDataType::Integer
@ Integer
Definition tagvalue.h:121
TagParser::TagDataType::Popularity
@ Popularity
Definition tagvalue.h:129
TagParser::TagDataType::Undefined
@ Undefined
Definition tagvalue.h:128
TagParser::FrameComparer::operator()
bool operator()(std::uint32_t lhs, std::uint32_t rhs) const
Returns true if lhs goes before rhs; otherwise returns false.
Definition id3v2tag.cpp:710