Tag Parser 11.2.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
id3v2frame.cpp
Go to the documentation of this file.
1#include "./id3v2frame.h"
2#include "./id3genres.h"
3#include "./id3v2frameids.h"
4
5#include "../diagnostics.h"
6#include "../exceptions.h"
7
8#include <c++utilities/conversion/stringbuilder.h>
9#include <c++utilities/conversion/stringconversion.h>
10
11#include <zlib.h>
12
13#include <algorithm>
14#include <cstdint>
15#include <cstring>
16#include <limits>
17#include <memory>
18
19using namespace std;
20using namespace CppUtilities;
21namespace TagParser {
22
24namespace Id3v2TextEncodingBytes {
25enum Id3v2TextEncodingByte : std::uint8_t { Ascii, Utf16WithBom, Utf16BigEndianWithoutBom, Utf8 };
26}
28
30constexpr auto maxId3v2FrameDataSize(numeric_limits<std::uint32_t>::max() - 15);
31
41 : m_parsedVersion(0)
42 , m_dataSize(0)
43 , m_totalSize(0)
44 , m_flag(0)
45 , m_group(0)
46 , m_padding(false)
47{
48}
49
53Id3v2Frame::Id3v2Frame(const IdentifierType &id, const TagValue &value, std::uint8_t group, std::uint16_t flag)
54 : TagField<Id3v2Frame>(id, value)
55 , m_parsedVersion(0)
56 , m_dataSize(0)
57 , m_totalSize(0)
58 , m_flag(flag)
59 , m_group(group)
60 , m_padding(false)
61{
62}
63
68template <class stringtype> int parseGenreIndex(const stringtype &denotation)
69{
70 int index = -1;
71 for (auto c : denotation) {
72 if (index == -1) {
73 switch (c) {
74 case ' ':
75 break;
76 case '(':
77 index = 0;
78 break;
79 case '\0':
80 return -1;
81 default:
82 if (c >= '0' && c <= '9') {
83 index = c - '0';
84 } else {
85 return -1;
86 }
87 }
88 } else {
89 switch (c) {
90 case ')':
91 return index;
92 case '\0':
93 return index;
94 default:
95 if (c >= '0' && c <= '9') {
96 index = index * 10 + c - '0';
97 } else {
98 return -1;
99 }
100 }
101 }
102 }
103 return index;
104}
105
109string stringFromSubstring(tuple<const char *, size_t, const char *> substr)
110{
111 return string(get<0>(substr), get<1>(substr));
112}
113
117u16string wideStringFromSubstring(tuple<const char *, size_t, const char *> substr, TagTextEncoding encoding)
118{
119 u16string res(reinterpret_cast<u16string::const_pointer>(get<0>(substr)), get<1>(substr) / 2);
120 TagValue::ensureHostByteOrder(res, encoding);
121 return res;
122}
123
134void Id3v2Frame::parse(BinaryReader &reader, std::uint32_t version, std::uint32_t maximalSize, Diagnostics &diag)
135{
136 static const string defaultContext("parsing ID3v2 frame");
137 string context;
138
139 // parse header
140 if (version < 3) {
141 // parse header for ID3v2.1 and ID3v2.2
142 // -> read ID
143 setId(reader.readUInt24BE());
144 if (id() & 0xFFFF0000u) {
145 m_padding = false;
146 } else {
147 // padding reached
148 m_padding = true;
149 throw NoDataFoundException();
150 }
151
152 // -> update context
153 context = "parsing " % idToString() + " frame";
154
155 // -> read size, check whether frame is truncated
156 m_dataSize = reader.readUInt24BE();
157 m_totalSize = m_dataSize + 6;
158 if (m_totalSize > maximalSize) {
159 diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
161 }
162
163 // -> no flags/group in ID3v2.2
164 m_flag = 0;
165 m_group = 0;
166
167 } else {
168 // parse header for ID3v2.3 and ID3v2.4
169 // -> read ID
170 setId(reader.readUInt32BE());
171 if (id() & 0xFF000000u) {
172 m_padding = false;
173 } else {
174 // padding reached
175 m_padding = true;
176 throw NoDataFoundException();
177 }
178
179 // -> update context
180 context = "parsing " % idToString() + " frame";
181
182 // -> read size, check whether frame is truncated
183 m_dataSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
184 m_totalSize = m_dataSize + 10;
185 if (m_totalSize > maximalSize) {
186 diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
188 }
189
190 // -> read flags and group
191 m_flag = reader.readUInt16BE();
192 m_group = hasGroupInformation() ? reader.readByte() : 0;
193 if (isEncrypted()) {
194 // encryption is not implemented
195 diag.emplace_back(DiagLevel::Critical, "Encrypted frames aren't supported.", context);
197 }
198 }
199
200 // add a warning if a frame appears in an ID3v2 tag known not to support it
202 diag.emplace_back(DiagLevel::Warning,
203 argsToString("The frame is only supported in ID3v2.4 and newer but the tag's version is ID3v2.", version, '.'), context);
204 } else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(id())) {
205 diag.emplace_back(DiagLevel::Warning,
206 argsToString("The frame is only supported in ID3v2.3 and older but the tag's version is ID3v2.", version, '.'), context);
207 }
208
209 // frame size mustn't be 0
210 if (m_dataSize <= 0) {
211 diag.emplace_back(DiagLevel::Warning, "The frame size is 0.", context);
212 throw InvalidDataException();
213 }
214
215 // parse the data
216 unique_ptr<char[]> buffer;
217
218 // -> decompress data if compressed; otherwise just read it
219 if (isCompressed()) {
220 uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
221 if (decompressedSize < m_dataSize) {
222 diag.emplace_back(DiagLevel::Critical, "The decompressed size is smaller than the compressed size.", context);
223 throw InvalidDataException();
224 }
225 const auto bufferCompressed = make_unique<char[]>(m_dataSize);
226 reader.read(bufferCompressed.get(), m_dataSize);
227 buffer = make_unique<char[]>(decompressedSize);
228 switch (
229 uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
230 case Z_MEM_ERROR:
231 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
232 throw InvalidDataException();
233 case Z_BUF_ERROR:
234 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
235 throw InvalidDataException();
236 case Z_DATA_ERROR:
237 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
238 throw InvalidDataException();
239 case Z_OK:
240 break;
241 default:
242 diag.emplace_back(DiagLevel::Critical, "Decompressing failed (unknown reason).", context);
243 throw InvalidDataException();
244 }
245 if (decompressedSize > maxId3v2FrameDataSize) {
246 diag.emplace_back(DiagLevel::Critical, "The decompressed data exceeds the maximum supported frame size.", context);
247 throw InvalidDataException();
248 }
249 m_dataSize = static_cast<std::uint32_t>(decompressedSize);
250 } else {
251 buffer = make_unique<char[]>(m_dataSize);
252 reader.read(buffer.get(), m_dataSize);
253 }
254
255 // read tag value depending on frame ID/type
256 if (Id3v2FrameIds::isTextFrame(id())) {
257 // parse text encoding byte
258 TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer.get()), diag);
259
260 // parse string values (since ID3v2.4 a text frame may contain multiple strings)
261 const char *currentOffset = buffer.get() + 1;
262 for (size_t currentIndex = 1; currentIndex < m_dataSize;) {
263 // determine the next substring
264 const auto substr(parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding, false, diag));
265
266 // handle case when string is empty
267 if (!get<1>(substr)) {
268 if (currentIndex == 1) {
270 }
271 currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
272 currentOffset = get<2>(substr);
273 continue;
274 }
275
276 // determine the TagValue instance to store the value
277 TagValue *const value = [&] {
278 if (this->value().isEmpty()) {
279 return &this->value();
280 }
281 m_additionalValues.emplace_back();
282 return &m_additionalValues.back();
283 }();
284
285 // apply further parsing for some text frame types (eg. convert track number to PositionInSet)
288 // parse the track number or the disk number frame
289 try {
290 if (characterSize(dataEncoding) > 1) {
292 } else {
294 }
295 } catch (const ConversionException &) {
296 diag.emplace_back(DiagLevel::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
297 }
298
299 } else if ((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
300 // parse frame contains length
301 try {
302 const auto milliseconds = [&] {
303 if (dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
304 const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
305 const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
306 ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
307 : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
308 return string(convertedStringData.first.get(), convertedStringData.second);
309 } else { // Latin-1 or UTF-8
310 return stringFromSubstring(substr);
311 }
312 }();
313 value->assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
314 } catch (const ConversionException &) {
315 diag.emplace_back(DiagLevel::Warning, "The value of the length frame is not numeric and will be ignored.", context);
316 }
317
318 } else if ((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
319 // parse genre/content type
320 const auto genreIndex = [&] {
321 if (characterSize(dataEncoding) > 1) {
322 return parseGenreIndex(wideStringFromSubstring(substr, dataEncoding));
323 } else {
324 return parseGenreIndex(stringFromSubstring(substr));
325 }
326 }();
327 if (genreIndex != -1) {
328 // genre is specified as ID3 genre number
329 value->assignStandardGenreIndex(genreIndex);
330 } else {
331 // genre is specified as string
332 value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
333 }
334 } else {
335 // store any other text frames as-is
336 value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
337 }
338
339 currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
340 currentOffset = get<2>(substr);
341 }
342
343 // add warning about additional values
344 if (version < 4 && !m_additionalValues.empty()) {
345 diag.emplace_back(
346 DiagLevel::Warning, "Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
347 }
348
349 } else if (version >= 3 && id() == Id3v2FrameIds::lCover) {
350 // parse picture frame
351 std::uint8_t type;
352 parsePicture(buffer.get(), m_dataSize, value(), type, diag);
353 setTypeInfo(type);
354
355 } else if (version < 3 && id() == Id3v2FrameIds::sCover) {
356 // parse legacy picutre
357 std::uint8_t type;
358 parseLegacyPicture(buffer.get(), m_dataSize, value(), type, diag);
359 setTypeInfo(type);
360
361 } else if (((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
363 // parse comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
364 parseComment(buffer.get(), m_dataSize, value(), diag);
365
366 } else {
367 // parse unknown/unsupported frame
368 value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
369 }
370}
371
383{
384 return Id3v2FrameMaker(*this, version, diag);
385}
386
395void Id3v2Frame::make(BinaryWriter &writer, std::uint8_t version, Diagnostics &diag)
396{
397 prepareMaking(version, diag).make(writer);
398}
399
403void Id3v2Frame::internallyClearValue()
404{
406 m_additionalValues.clear();
407}
408
412void Id3v2Frame::internallyClearFurtherData()
413{
414 m_flag = 0;
415 m_group = 0;
416 m_parsedVersion = 0;
417 m_dataSize = 0;
418 m_totalSize = 0;
419 m_padding = false;
420}
421
425std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg() const
426{
427 if (m_additionalValues.size() == 1) {
428 return argsToString("Additional value \"", m_additionalValues.front().toString(TagTextEncoding::Utf8), "\" is supposed to be ignored.");
429 }
430 return argsToString("Additional values ", DiagMessage::formatList(TagValue::toStrings(m_additionalValues)), " are supposed to be ignored.");
431}
432
444Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t version, Diagnostics &diag)
445 : m_frame(frame)
446 , m_frameId(m_frame.id())
447 , m_version(version)
448{
449 const string context("making " % m_frame.idToString() + " frame");
450
451 // validate frame's configuration
452 if (m_frame.isEncrypted()) {
453 diag.emplace_back(DiagLevel::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
454 throw InvalidDataException();
455 }
456 if (m_frame.hasPaddingReached()) {
457 diag.emplace_back(DiagLevel::Critical, "Cannot make a frame which is marked as padding.", context);
458 throw InvalidDataException();
459 }
460 if (version < 3 && m_frame.isCompressed()) {
461 diag.emplace_back(DiagLevel::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
462 }
463 if (version < 3 && (m_frame.flag() || m_frame.group())) {
464 diag.emplace_back(DiagLevel::Warning,
465 "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
466 }
467
468 // get non-empty, assigned values
469 vector<const TagValue *> values;
470 values.reserve(1 + frame.additionalValues().size());
471 if (!frame.value().isEmpty()) {
472 values.emplace_back(&frame.value());
473 }
474 for (const auto &value : frame.additionalValues()) {
475 if (!value.isEmpty()) {
476 values.emplace_back(&value);
477 }
478 }
479
480 // validate assigned values
481 if (values.empty()) {
482 throw NoDataProvidedException();
483 // note: This is not really an issue because in the case we're not provided with any value here just means that the field
484 // is supposed to be removed. So don't add any diagnostic messages here.
485 }
486 const bool isTextFrame = Id3v2FrameIds::isTextFrame(m_frameId);
487 if (values.size() != 1) {
488 if (!isTextFrame) {
489 diag.emplace_back(DiagLevel::Critical, "Multiple values are not supported for non-text-frames.", context);
490 throw InvalidDataException();
491 } else if (version < 4) {
492 diag.emplace_back(
493 DiagLevel::Warning, "Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
494 }
495 }
496
497 // convert frame ID if necessary
498 if (version >= 3) {
499 if (Id3v2FrameIds::isShortId(m_frameId)) {
500 // try to convert the short frame ID to its long equivalent
501 if (!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
502 diag.emplace_back(DiagLevel::Critical,
503 "The short frame ID can't be converted to its long equivalent which is needed to use the frame in a newer version of ID3v2.",
504 context);
505 throw InvalidDataException();
506 }
507 }
508 } else {
509 if (Id3v2FrameIds::isLongId(m_frameId)) {
510 // try to convert the long frame ID to its short equivalent
511 if (!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
512 diag.emplace_back(DiagLevel::Critical,
513 "The long frame ID can't be converted to its short equivalent which is needed to use the frame in the old version of ID3v2.",
514 context);
515 throw InvalidDataException();
516 }
517 }
518 }
519
520 // add a warning if we're writing the frame for an ID3v2 tag known not to support it
521 if (version <= 3 && Id3v2FrameIds::isOnlyId3v24Id(version < 3 ? Id3v2FrameIds::convertToLongId(m_frameId) : m_frameId)) {
522 diag.emplace_back(DiagLevel::Warning,
523 argsToString("The frame is only supported in ID3v2.4 and newer but version of the tag being written is ID3v2.", version,
524 ". The frame is written nevertheless but other tools might not be able to deal with it."),
525 context);
526 } else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(m_frameId)) {
527 diag.emplace_back(DiagLevel::Warning,
528 argsToString("The frame is only supported in ID3v2.3 and older but version of the tag being written is ID3v2.", version,
529 ". The frame is written nevertheless but other tools might not be able to deal with it."),
530 context);
531 }
532
533 // make actual data depending on the frame ID
534 try {
535 if (isTextFrame) {
536 // make text frame
537 vector<string> substrings;
538 substrings.reserve(1 + frame.additionalValues().size());
540
541 if ((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
542 || (version < 3 && (m_frameId == Id3v2FrameIds::sTrackPosition || m_frameId == Id3v2FrameIds::sDiskPosition))) {
543 // make track number or disk number frame
545 for (const auto *const value : values) {
546 // convert the position to string
547 substrings.emplace_back(value->toString(encoding));
548 // warn if value is no valid position (although we just store a string after all)
550 continue;
551 }
552 try {
554 } catch (const ConversionException &) {
555 diag.emplace_back(DiagLevel::Warning,
556 argsToString("The track/disk number \"", substrings.back(), "\" is not of the expected form, eg. \"4/10\"."), context);
557 }
558 }
559
560 } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lLength) || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
561 // make length frame
562 encoding = TagTextEncoding::Latin1;
563 for (const auto *const value : values) {
564 const auto duration(value->toTimeSpan());
565 if (duration.isNegative()) {
566 diag.emplace_back(DiagLevel::Critical, argsToString("Assigned duration \"", duration.toString(), "\" is negative."), context);
567 throw InvalidDataException();
568 }
569 substrings.emplace_back(numberToString(static_cast<std::uint64_t>(duration.totalMilliseconds())));
570 }
571
572 } else {
573 // make standard genre index and other text frames
574 // -> find text encoding suitable for all assigned values
575 for (const auto *const value : values) {
576 switch (encoding) {
578 switch (value->type()) {
580 encoding = TagTextEncoding::Latin1;
581 break;
582 default:
583 encoding = value->dataEncoding();
584 }
585 break;
587 switch (value->dataEncoding()) {
589 break;
590 default:
591 encoding = value->dataEncoding();
592 }
593 break;
594 default:;
595 }
596 }
597 if (version <= 3 && encoding == TagTextEncoding::Utf8) {
599 }
600 // -> format values
601 for (const auto *const value : values) {
603 && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre) || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
604 // make standard genere index
605 substrings.emplace_back(numberToString(value->toStandardGenreIndex()));
606
607 } else {
608 // make other text frame
609 substrings.emplace_back(value->toString(encoding));
610 }
611 }
612 }
613
614 // concatenate substrings using encoding specific byte order mark and termination
615 const auto terminationLength = (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) ? 2u : 1u;
616 const auto byteOrderMark = [&] {
617 switch (encoding) {
619 return string({ '\xFF', '\xFE' });
621 return string({ '\xFE', '\xFF' });
622 default:
623 return string();
624 }
625 }();
626 const auto concatenatedSubstrings = joinStrings(substrings, string(), false, byteOrderMark, string(terminationLength, '\0'));
627
628 // write text encoding byte and concatenated strings to data buffer
629 m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(1 + concatenatedSubstrings.size()));
630 m_data[0] = static_cast<char>(Id3v2Frame::makeTextEncodingByte(encoding));
631 concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
632
633 } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lCover) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
634 // make picture frame
635 m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version, diag);
636
637 } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lComment) || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
638 || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
639 || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
640 // make comment frame or the unsynchronized lyrics frame
641 m_frame.makeComment(m_data, m_decompressedSize, *values.front(), version, diag);
642
643 } else {
644 // make unknown frame
645 const auto &value(*values.front());
647 diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds maximum size.", context);
648 throw InvalidDataException();
649 }
650 m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(value.dataSize()));
651 copy(value.dataPointer(), value.dataPointer() + m_decompressedSize, m_data.get());
652 }
653 } catch (const ConversionException &) {
654 try {
655 const auto valuesAsString = TagValue::toStrings(values);
656 diag.emplace_back(DiagLevel::Critical,
657 argsToString("Assigned value(s) \"", DiagMessage::formatList(valuesAsString), "\" can not be converted appropriately."), context);
658 } catch (const ConversionException &) {
659 diag.emplace_back(DiagLevel::Critical, "Assigned value(s) can not be converted appropriately.", context);
660 }
661 throw InvalidDataException();
662 }
663
664 // apply compression if frame should be compressed
665 if (version >= 3 && m_frame.isCompressed()) {
666 auto compressedSize = compressBound(m_decompressedSize);
667 auto compressedData = make_unique<char[]>(compressedSize);
668 switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
669 reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
670 case Z_MEM_ERROR:
671 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
672 throw InvalidDataException();
673 case Z_BUF_ERROR:
674 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
675 throw InvalidDataException();
676 case Z_OK:;
677 }
678 if (compressedSize > maxId3v2FrameDataSize) {
679 diag.emplace_back(DiagLevel::Critical, "Compressed size exceeds maximum data size.", context);
680 throw InvalidDataException();
681 }
682 m_data.swap(compressedData);
683 m_dataSize = static_cast<std::uint32_t>(compressedSize);
684 } else {
685 m_dataSize = m_decompressedSize;
686 }
687
688 // calculate required size
689 // -> data size
690 m_requiredSize = m_dataSize;
691 if (version < 3) {
692 // -> header size
693 m_requiredSize += 6;
694 } else {
695 // -> header size
696 m_requiredSize += 10;
697 // -> group byte
698 if (m_frame.hasGroupInformation()) {
699 m_requiredSize += 1;
700 }
701 // -> decompressed size
702 if (version >= 3 && m_frame.isCompressed()) {
703 m_requiredSize += 4;
704 }
705 }
706}
707
715void Id3v2FrameMaker::make(BinaryWriter &writer)
716{
717 if (m_version < 3) {
718 writer.writeUInt24BE(m_frameId);
719 writer.writeUInt24BE(m_dataSize);
720 } else {
721 writer.writeUInt32BE(m_frameId);
722 if (m_version >= 4) {
723 writer.writeSynchsafeUInt32BE(m_dataSize);
724 } else {
725 writer.writeUInt32BE(m_dataSize);
726 }
727 writer.writeUInt16BE(m_frame.flag());
728 if (m_frame.hasGroupInformation()) {
729 writer.writeByte(m_frame.group());
730 }
731 if (m_version >= 3 && m_frame.isCompressed()) {
732 if (m_version >= 4) {
733 writer.writeSynchsafeUInt32BE(m_decompressedSize);
734 } else {
735 writer.writeUInt32BE(m_decompressedSize);
736 }
737 }
738 }
739 writer.write(m_data.get(), m_dataSize);
740}
741
749{
750 switch (textEncodingByte) {
751 case Id3v2TextEncodingBytes::Ascii:
753 case Id3v2TextEncodingBytes::Utf16WithBom:
755 case Id3v2TextEncodingBytes::Utf16BigEndianWithoutBom:
759 default:
760 diag.emplace_back(
761 DiagLevel::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + idToString());
763 }
764}
765
770{
771 switch (textEncoding) {
773 return Id3v2TextEncodingBytes::Ascii;
777 return Id3v2TextEncodingBytes::Utf16WithBom;
779 return Id3v2TextEncodingBytes::Utf16WithBom;
780 default:
781 return 0;
782 }
783}
784
799tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(
800 const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
801{
802 tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
803 switch (encoding) {
807 if ((bufferSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
808 if (encoding == TagTextEncoding::Latin1) {
809 diag.emplace_back(DiagLevel::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
810 "parsing frame " + idToString());
811 encoding = TagTextEncoding::Utf8;
812 }
813 get<0>(res) += 3;
814 }
815 const char *pos = get<0>(res);
816 for (; *pos != 0x00; ++pos) {
817 if (pos < get<2>(res)) {
818 ++get<1>(res);
819 } else {
820 if (addWarnings) {
821 diag.emplace_back(
822 DiagLevel::Warning, "String in frame is not terminated properly.", "parsing termination of frame " + idToString());
823 }
824 break;
825 }
826 }
827 get<2>(res) = pos + 1;
828 break;
829 }
832 if (bufferSize >= 2) {
833 switch (LE::toUInt16(buffer)) {
834 case 0xFEFF:
835 if (encoding == TagTextEncoding::Utf16BigEndian) {
836 diag.emplace_back(DiagLevel::Critical,
837 "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
838 "parsing frame " + idToString());
840 }
841 get<0>(res) += 2;
842 break;
843 case 0xFFFE:
845 get<0>(res) += 2;
846 }
847 }
848 const std::uint16_t *pos = reinterpret_cast<const std::uint16_t *>(get<0>(res));
849 for (; *pos != 0x0000; ++pos) {
850 if (pos < reinterpret_cast<const std::uint16_t *>(get<2>(res))) {
851 get<1>(res) += 2;
852 } else {
853 if (addWarnings) {
854 diag.emplace_back(
855 DiagLevel::Warning, "Wide string in frame is not terminated properly.", "parsing termination of frame " + idToString());
856 }
857 break;
858 }
859 }
860 get<2>(res) = reinterpret_cast<const char *>(pos + 1);
861 break;
862 }
863 }
864 return res;
865}
866
872string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
873{
874 return stringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag));
875}
876
884u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
885{
886 return wideStringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag), encoding);
887}
888
896void Id3v2Frame::parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
897{
898 switch (encoding) {
901 if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
903 } else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
905 }
906 break;
907 default:
908 if ((maxSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
909 encoding = TagTextEncoding::Utf8;
910 diag.emplace_back(DiagLevel::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte order mark of frame " + idToString());
911 }
912 }
913}
914
922void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
923{
924 static const string context("parsing ID3v2.2 picture frame");
925 if (maxSize < 6) {
926 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete.", context);
928 }
929 const char *end = buffer + maxSize;
930 auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
931 typeInfo = static_cast<unsigned char>(*(buffer + 4));
932 auto substr = parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding, true, diag);
933 tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
934 if (get<2>(substr) >= end) {
935 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
937 }
938 tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
939}
940
948void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
949{
950 static const string context("parsing ID3v2.3 picture frame");
951 const char *end = buffer + maxSize;
952 auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
953 auto mimeTypeEncoding = TagTextEncoding::Latin1;
954 auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true, diag);
955 if (get<1>(substr)) {
956 tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
957 }
958 if (get<2>(substr) >= end) {
959 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
961 }
962 typeInfo = static_cast<unsigned char>(*get<2>(substr));
963 if (++get<2>(substr) >= end) {
964 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
966 }
967 substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, true, diag);
968 tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
969 if (get<2>(substr) >= end) {
970 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
972 }
973 tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
974}
975
982void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue, Diagnostics &diag)
983{
984 static const string context("parsing comment/unsynchronized lyrics frame");
985 const char *end = buffer + dataSize;
986 if (dataSize < 5) {
987 diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete.", context);
989 }
990 TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag);
991 if (*(++buffer)) {
992 tagValue.setLocale(Locale(std::string(buffer, 3), LocaleFormat::ISO_639_2_B)); // does standard say whether T or B?
993 }
994 auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true, diag);
995 tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
996 if (get<2>(substr) > end) {
997 diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete (description not terminated?).", context);
999 }
1000 substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, false, diag);
1001 tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
1002}
1003
1008size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
1009{
1010 switch (encoding) {
1012 LE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1013 return 2;
1015 BE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1016 return 2;
1017 default:
1018 return 0;
1019 }
1020}
1021
1026 unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, Diagnostics &diag)
1027{
1028 // determine description
1029 TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1030 StringData convertedDescription;
1031 string::size_type descriptionSize = picture.description().find(
1032 "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1033 if (descriptionSize == string::npos) {
1034 descriptionSize = picture.description().size();
1035 }
1036 if (descriptionEncoding == TagTextEncoding::Utf8) {
1037 // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1038 descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1039 convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1040 descriptionSize = convertedDescription.second;
1041 }
1042
1043 // calculate needed buffer size and create buffer
1044 // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
1045 const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1046 + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1047 + picture.dataSize();
1048 if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
1049 diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making legacy picture frame");
1050 throw InvalidDataException();
1051 }
1052 buffer = make_unique<char[]>(bufferSize = static_cast<std::uint32_t>(requiredBufferSize));
1053 char *offset = buffer.get();
1054
1055 // write encoding byte
1056 *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1057
1058 // write mime type
1059 const char *imageFormat;
1060 if (picture.mimeType() == "image/jpeg") {
1061 imageFormat = "JPG";
1062 } else if (picture.mimeType() == "image/png") {
1063 imageFormat = "PNG";
1064 } else if (picture.mimeType() == "image/gif") {
1065 imageFormat = "GIF";
1066 } else if (picture.mimeType() == "-->") {
1067 imageFormat = picture.mimeType().data();
1068 } else {
1069 imageFormat = "UND";
1070 }
1071 strncpy(++offset, imageFormat, 3);
1072
1073 // write picture type
1074 *(offset += 3) = static_cast<char>(typeInfo);
1075
1076 // write description
1077 offset += makeBom(offset + 1, descriptionEncoding);
1078 if (convertedDescription.first) {
1079 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1080 } else {
1081 picture.description().copy(++offset, descriptionSize);
1082 }
1083 *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1084 if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1085 *(++offset) = 0x00;
1086 }
1087
1088 // write actual data
1089 copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1090}
1091
1095void Id3v2Frame::makePicture(std::unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo,
1096 std::uint8_t version, Diagnostics &diag)
1097{
1098 if (version < 3) {
1099 makeLegacyPicture(buffer, bufferSize, picture, typeInfo, diag);
1100 return;
1101 }
1102
1103 // determine description
1104 TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1105 StringData convertedDescription;
1106 string::size_type descriptionSize = picture.description().find(
1107 "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1108 if (descriptionSize == string::npos) {
1109 descriptionSize = picture.description().size();
1110 }
1111 if (version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
1112 // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1113 descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1114 convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1115 descriptionSize = convertedDescription.second;
1116 }
1117 // determine mime-type
1118 string::size_type mimeTypeSize = picture.mimeType().find('\0');
1119 if (mimeTypeSize == string::npos) {
1120 mimeTypeSize = picture.mimeType().length();
1121 }
1122
1123 // calculate needed buffer size and create buffer
1124 // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
1125 const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1126 + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1127 + picture.dataSize();
1128 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1129 diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making picture frame");
1130 throw InvalidDataException();
1131 }
1132 buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1133 char *offset = buffer.get();
1134
1135 // write encoding byte
1136 *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1137
1138 // write mime type
1139 picture.mimeType().copy(++offset, mimeTypeSize);
1140
1141 *(offset += mimeTypeSize) = 0x00; // terminate mime type
1142 // write picture type
1143 *(++offset) = static_cast<char>(typeInfo);
1144
1145 // write description
1146 offset += makeBom(offset + 1, descriptionEncoding);
1147 if (convertedDescription.first) {
1148 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1149 } else {
1150 picture.description().copy(++offset, descriptionSize);
1151 }
1152 *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1153 if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1154 *(++offset) = 0x00;
1155 }
1156
1157 // write actual data
1158 copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1159}
1160
1164void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &comment, std::uint8_t version, Diagnostics &diag)
1165{
1166 static const string context("making comment frame");
1167
1168 // check whether type and other values are valid
1169 TagTextEncoding encoding = comment.dataEncoding();
1170 if (!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1171 diag.emplace_back(DiagLevel::Critical, "Data encoding and description encoding aren't equal.", context);
1172 throw InvalidDataException();
1173 }
1175 if (language.length() > 3) {
1176 diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1177 throw InvalidDataException();
1178 }
1179 StringData convertedDescription;
1180 string::size_type descriptionSize = comment.description().find(
1181 "\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1182 if (descriptionSize == string::npos) {
1183 descriptionSize = comment.description().size();
1184 }
1185 if (version < 4 && encoding == TagTextEncoding::Utf8) {
1186 // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1188 convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1189 descriptionSize = convertedDescription.second;
1190 }
1191
1192 // calculate needed buffer size and create buffer
1193 // note: encoding byte + language + description size + actual data size + BOMs and termination
1194 const auto data = comment.toString(encoding);
1195 const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
1196 + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size();
1197 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1198 diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", context);
1199 throw InvalidDataException();
1200 }
1201 buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1202 char *offset = buffer.get();
1203
1204 // write encoding
1205 *offset = static_cast<char>(makeTextEncodingByte(encoding));
1206
1207 // write language
1208 for (unsigned int i = 0; i < 3; ++i) {
1209 *(++offset) = (language.length() > i) ? language[i] : 0x00;
1210 }
1211
1212 // write description
1213 offset += makeBom(offset + 1, encoding);
1214 if (convertedDescription.first) {
1215 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1216 } else {
1217 comment.description().copy(++offset, descriptionSize);
1218 }
1219 offset += descriptionSize;
1220 *offset = 0x00; // terminate description and increase data offset
1222 *(++offset) = 0x00;
1223 }
1224
1225 // write actual data
1226 offset += makeBom(offset + 1, encoding);
1227 data.copy(++offset, data.size());
1228}
1229
1230} // namespace TagParser
static std::string formatList(const std::vector< std::string > &values)
Concatenates the specified string values to a list.
Definition: diagnostics.cpp:69
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The Id3v2FrameMaker class helps making ID3v2 frames.
Definition: id3v2frame.h:22
void make(CppUtilities::BinaryWriter &writer)
Saves the frame (specified when constructing the object) using the specified writer.
Definition: id3v2frame.cpp:715
The Id3v2Frame class is used by Id3v2Tag to store the fields.
Definition: id3v2frame.h:86
bool isEncrypted() const
Returns whether the frame is encrypted.
Definition: id3v2frame.h:270
static std::size_t makeBom(char *buffer, TagTextEncoding encoding)
Writes the BOM for the specified encoding to the specified buffer.
std::uint32_t dataSize() const
Returns the size of the data stored in the frame in bytes.
Definition: id3v2frame.h:229
static std::uint8_t makeTextEncodingByte(TagTextEncoding textEncoding)
Returns a text encoding byte for the specified textEncoding.
Definition: id3v2frame.cpp:769
std::string parseString(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring from the specified buffer.
Definition: id3v2frame.cpp:872
void parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
Parses a byte order mark from the specified buffer.
Definition: id3v2frame.cpp:896
static void makeComment(std::unique_ptr< char[]> &buffer, std::uint32_t &bufferSize, const TagValue &comment, std::uint8_t version, Diagnostics &diag)
Writes the specified comment to the specified buffer.
void parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
Parses the ID3v2.2 picture from the specified buffer.
Definition: id3v2frame.cpp:922
void parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
Parses the ID3v2.3 picture from the specified buffer.
Definition: id3v2frame.cpp:948
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:134
std::tuple< const char *, std::size_t, const char * > parseSubstring(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring from the specified buffer.
Definition: id3v2frame.cpp:799
bool hasGroupInformation() const
Returns whether the frame contains group information.
Definition: id3v2frame.h:278
std::uint16_t flag() const
Returns the flags.
Definition: id3v2frame.h:205
Id3v2Frame()
Constructs a new Id3v2Frame.
Definition: id3v2frame.cpp:40
bool isCompressed() const
Returns whether the frame is compressed.
Definition: id3v2frame.h:261
static void makePicture(std::unique_ptr< char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, std::uint8_t version, Diagnostics &diag)
Writes the specified picture to the specified buffer.
TagTextEncoding parseTextEncodingByte(std::uint8_t textEncodingByte, Diagnostics &diag)
Returns the text encoding for the specified textEncodingByte.
Definition: id3v2frame.cpp:748
Id3v2FrameMaker prepareMaking(std::uint8_t version, Diagnostics &diag)
Prepares making.
Definition: id3v2frame.cpp:382
void parseComment(const char *buffer, std::size_t maxSize, TagValue &tagValue, Diagnostics &diag)
Parses the comment/unsynchronized lyrics from the specified buffer.
Definition: id3v2frame.cpp:982
static void makeLegacyPicture(std::unique_ptr< char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, Diagnostics &diag)
Writes the specified picture to the specified buffer (ID3v2.2 compatible).
std::u16string parseWideString(const char *buffer, std::size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
Parses a substring from the specified buffer.
Definition: id3v2frame.cpp:884
void make(CppUtilities::BinaryWriter &writer, std::uint8_t version, Diagnostics &diag)
Writes the frame to a stream using the specified writer and the specified ID3v2 version.
Definition: id3v2frame.cpp:395
friend class Id3v2FrameMaker
Definition: id3v2frame.h:88
std::uint8_t group() const
Returns the group.
Definition: id3v2frame.h:303
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:21
The TagField class is used by FieldMapBasedTag to store the fields.
void setTypeInfo(const TypeInfoType &typeInfo)
Sets the type info of the current TagField.
const TypeInfoType & typeInfo() const
Returns the type info of the current TagField.
IdentifierType & id()
Returns the id of the current TagField.
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
std::string idToString() const
Returns the id of the current TagField as string.
TagValue & value()
Returns the value of the current TagField.
The TagValue class wraps values of different types.
Definition: tagvalue.h:95
void setMimeType(std::string_view mimeType)
Sets the MIME type.
Definition: tagvalue.h:637
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:625
static void ensureHostByteOrder(std::u16string &u16str, TagTextEncoding currentEncoding)
Ensures the byte-order of the specified UTF-16 string matches the byte-order of the machine.
Definition: tagvalue.cpp:908
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:753
void assignPosition(PositionInSet value)
Assigns the given PositionInSet value.
Definition: tagvalue.h:428
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
void setDescription(std::string_view value, TagTextEncoding encoding=TagTextEncoding::Latin1)
Sets the description.
Definition: tagvalue.h:612
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
Definition: tagvalue.cpp:401
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:468
void assignTimeSpan(CppUtilities::TimeSpan value)
Assigns the given TimeSpan value.
Definition: tagvalue.h:441
static std::vector< std::string > toStrings(const ContainerType &values, TagTextEncoding encoding=TagTextEncoding::Utf8)
Converts the specified values to string using the specified encoding.
Definition: tagvalue.h:776
void clearDataAndMetadata()
Wipes assigned data including meta data.
Definition: tagvalue.h:547
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:557
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:763
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.h:459
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
Definition: tagvalue.h:485
void setLocale(const Locale &locale)
Sets the setLocale.
Definition: tagvalue.h:679
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:525
const std::string & description() const
Returns the description.
Definition: tagvalue.h:596
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
Definition: tagvalue.cpp:360
CppUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:439
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:568
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:39
The exception that is thrown when an operation fails because the detected or specified version is not...
Definition: exceptions.h:53
TAG_PARSER_EXPORT bool isPreId3v24Id(std::uint32_t id)
TAG_PARSER_EXPORT std::uint32_t convertToShortId(std::uint32_t id)
Converts the specified long frame ID to the equivalent short frame ID.
constexpr bool isLongId(std::uint32_t id)
Returns an indication whether the specified id is a long frame id.
Definition: id3v2frameids.h:93
constexpr bool isTextFrame(std::uint32_t id)
Returns an indication whether the specified id is a text frame id.
constexpr bool isShortId(std::uint32_t id)
Returns an indication whether the specified id is a short frame id.
TAG_PARSER_EXPORT bool isOnlyId3v24Id(std::uint32_t id)
TAG_PARSER_EXPORT std::uint32_t convertToLongId(std::uint32_t id)
Converts the specified short frame ID to the equivalent long frame ID.
constexpr TAG_PARSER_EXPORT std::string_view comment()
constexpr TAG_PARSER_EXPORT std::string_view language()
constexpr TAG_PARSER_EXPORT std::string_view duration()
constexpr TAG_PARSER_EXPORT std::string_view version()
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
constexpr int characterSize(TagTextEncoding encoding)
Returns the size of one character for the specified encoding in bytes.
Definition: tagvalue.h:57
u16string wideStringFromSubstring(tuple< const char *, size_t, const char * > substr, TagTextEncoding encoding)
Returns an std::u16string instance for the substring parsed using parseSubstring().
Definition: id3v2frame.cpp:117
constexpr auto maxId3v2FrameDataSize(numeric_limits< std::uint32_t >::max() - 15)
The maximum (supported) size of an ID3v2Frame.
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:28
int parseGenreIndex(const stringtype &denotation)
Helper function to parse the genre index.
Definition: id3v2frame.cpp:68
string stringFromSubstring(tuple< const char *, size_t, const char * > substr)
Returns an std::string instance for the substring parsed using parseSubstring().
Definition: id3v2frame.cpp:109
The Locale struct specifies a language and/or a country using one or more LocaleDetail objects.
Definition: localehelper.h:61