Tag Parser 11.3.0
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#include "../tagtype.h"
8
9#include <c++utilities/conversion/stringbuilder.h>
10#include <c++utilities/conversion/stringconversion.h>
11
12#include <zlib.h>
13
14#include <algorithm>
15#include <cstdint>
16#include <cstring>
17#include <limits>
18#include <memory>
19
20using namespace std;
21using namespace CppUtilities;
22namespace TagParser {
23
25namespace Id3v2TextEncodingBytes {
26enum Id3v2TextEncodingByte : std::uint8_t { Ascii, Utf16WithBom, Utf16BigEndianWithoutBom, Utf8 };
27}
29
31constexpr auto maxId3v2FrameDataSize(numeric_limits<std::uint32_t>::max() - 15);
32
42 : m_parsedVersion(0)
43 , m_dataSize(0)
44 , m_totalSize(0)
45 , m_flag(0)
46 , m_group(0)
47 , m_padding(false)
48{
49}
50
54Id3v2Frame::Id3v2Frame(const IdentifierType &id, const TagValue &value, std::uint8_t group, std::uint16_t flag)
55 : TagField<Id3v2Frame>(id, value)
56 , m_parsedVersion(0)
57 , m_dataSize(0)
58 , m_totalSize(0)
59 , m_flag(flag)
60 , m_group(group)
61 , m_padding(false)
62{
63}
64
69template <class stringtype> static int parseGenreIndex(const stringtype &denotation)
70{
71 auto index = -1;
72 for (auto c : denotation) {
73 if (index == -1) {
74 switch (c) {
75 case ' ':
76 break;
77 case '(':
78 index = 0;
79 break;
80 case '\0':
81 return -1;
82 default:
83 if (c >= '0' && c <= '9') {
84 index = c - '0';
85 } else {
86 return -1;
87 }
88 }
89 } else {
90 switch (c) {
91 case ')':
92 return index;
93 case '\0':
94 return index;
95 default:
96 if (c >= '0' && c <= '9') {
97 index = index * 10 + c - '0';
98 } else {
99 return -1;
100 }
101 }
102 }
103 }
104 return index;
105}
106
110static std::string stringFromSubstring(std::tuple<const char *, std::size_t, const char *> substr)
111{
112 return std::string(std::get<0>(substr), std::get<1>(substr));
113}
114
118static std::u16string wideStringFromSubstring(std::tuple<const char *, std::size_t, const char *> substr, TagTextEncoding encoding)
119{
120 std::u16string res(reinterpret_cast<u16string::const_pointer>(std::get<0>(substr)), std::get<1>(substr) / 2);
121 TagValue::ensureHostByteOrder(res, encoding);
122 return res;
123}
124
128static std::uint64_t readPlayCounter(const char *begin, const char *end, const std::string &context, Diagnostics &diag)
129{
130 auto res = std::uint64_t();
131 auto pos = end - 1;
132 if (end - begin > 8) {
133 diag.emplace_back(DiagLevel::Critical, "Play counter is bigger than eight bytes and therefore not supported.", context);
134 return res;
135 }
136 for (auto shift = 0; pos >= begin; shift += 8, --pos) {
137 res += static_cast<std::uint64_t>(static_cast<std::uint8_t>(*pos)) << shift;
138 }
139 return res;
140}
141
152void Id3v2Frame::parse(BinaryReader &reader, std::uint32_t version, std::uint32_t maximalSize, Diagnostics &diag)
153{
154 static const string defaultContext("parsing ID3v2 frame");
155 string context;
156
157 // parse header
158 if (version < 3) {
159 // parse header for ID3v2.1 and ID3v2.2
160 // -> read ID
161 setId(reader.readUInt24BE());
162 if (id() & 0xFFFF0000u) {
163 m_padding = false;
164 } else {
165 // padding reached
166 m_padding = true;
167 throw NoDataFoundException();
168 }
169
170 // -> update context
171 context = "parsing " % idToString() + " frame";
172
173 // -> read size, check whether frame is truncated
174 m_dataSize = reader.readUInt24BE();
175 m_totalSize = m_dataSize + 6;
176 if (m_totalSize > maximalSize) {
177 diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
179 }
180
181 // -> no flags/group in ID3v2.2
182 m_flag = 0;
183 m_group = 0;
184
185 } else {
186 // parse header for ID3v2.3 and ID3v2.4
187 // -> read ID
188 setId(reader.readUInt32BE());
189 if (id() & 0xFF000000u) {
190 m_padding = false;
191 } else {
192 // padding reached
193 m_padding = true;
194 throw NoDataFoundException();
195 }
196
197 // -> update context
198 context = "parsing " % idToString() + " frame";
199
200 // -> read size, check whether frame is truncated
201 m_dataSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
202 m_totalSize = m_dataSize + 10;
203 if (m_totalSize > maximalSize) {
204 diag.emplace_back(DiagLevel::Warning, "The frame is truncated and will be ignored.", context);
206 }
207
208 // -> read flags and group
209 m_flag = reader.readUInt16BE();
210 m_group = hasGroupInformation() ? reader.readByte() : 0;
211 if (isEncrypted()) {
212 // encryption is not implemented
213 diag.emplace_back(DiagLevel::Critical, "Encrypted frames aren't supported.", context);
215 }
216 }
217
218 // add a warning if a frame appears in an ID3v2 tag known not to support it
220 diag.emplace_back(DiagLevel::Warning,
221 argsToString("The frame is only supported in ID3v2.4 and newer but the tag's version is ID3v2.", version, '.'), context);
222 } else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(id())) {
223 diag.emplace_back(DiagLevel::Warning,
224 argsToString("The frame is only supported in ID3v2.3 and older but the tag's version is ID3v2.", version, '.'), context);
225 }
226
227 // frame size mustn't be 0
228 if (m_dataSize <= 0) {
229 diag.emplace_back(DiagLevel::Warning, "The frame size is 0.", context);
230 throw InvalidDataException();
231 }
232
233 // parse the data
234 unique_ptr<char[]> buffer;
235
236 // -> decompress data if compressed; otherwise just read it
237 if (isCompressed()) {
238 uLongf decompressedSize = version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
239 if (decompressedSize < m_dataSize) {
240 diag.emplace_back(DiagLevel::Critical, "The decompressed size is smaller than the compressed size.", context);
241 throw InvalidDataException();
242 }
243 const auto bufferCompressed = make_unique<char[]>(m_dataSize);
244 reader.read(bufferCompressed.get(), m_dataSize);
245 buffer = make_unique<char[]>(decompressedSize);
246 switch (
247 uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
248 case Z_MEM_ERROR:
249 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
250 throw InvalidDataException();
251 case Z_BUF_ERROR:
252 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
253 throw InvalidDataException();
254 case Z_DATA_ERROR:
255 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The input data was corrupted or incomplete.", context);
256 throw InvalidDataException();
257 case Z_OK:
258 break;
259 default:
260 diag.emplace_back(DiagLevel::Critical, "Decompressing failed (unknown reason).", context);
261 throw InvalidDataException();
262 }
263 if (decompressedSize > maxId3v2FrameDataSize) {
264 diag.emplace_back(DiagLevel::Critical, "The decompressed data exceeds the maximum supported frame size.", context);
265 throw InvalidDataException();
266 }
267 m_dataSize = static_cast<std::uint32_t>(decompressedSize);
268 } else {
269 buffer = make_unique<char[]>(m_dataSize);
270 reader.read(buffer.get(), m_dataSize);
271 }
272
273 // read tag value depending on frame ID/type
274 if (Id3v2FrameIds::isTextFrame(id())) {
275 // parse text encoding byte
276 TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer.get()), diag);
277
278 // parse string values (since ID3v2.4 a text frame may contain multiple strings)
279 const char *currentOffset = buffer.get() + 1;
280 for (size_t currentIndex = 1; currentIndex < m_dataSize;) {
281 // determine the next substring
282 const auto substr(parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding, false, diag));
283
284 // handle case when string is empty
285 if (!get<1>(substr)) {
286 if (currentIndex == 1) {
288 }
289 currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
290 currentOffset = get<2>(substr);
291 continue;
292 }
293
294 // determine the TagValue instance to store the value
295 TagValue *const value = [&] {
296 if (this->value().isEmpty()) {
297 return &this->value();
298 }
299 m_additionalValues.emplace_back();
300 return &m_additionalValues.back();
301 }();
302
303 // apply further parsing for some text frame types (eg. convert track number to PositionInSet)
306 // parse the track number or the disk number frame
307 try {
308 if (characterSize(dataEncoding) > 1) {
309 value->assignPosition(PositionInSet(wideStringFromSubstring(substr, dataEncoding)));
310 } else {
311 value->assignPosition(PositionInSet(stringFromSubstring(substr)));
312 }
313 } catch (const ConversionException &) {
314 diag.emplace_back(DiagLevel::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
315 }
316
317 } else if ((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
318 // parse frame contains length
319 try {
320 const auto milliseconds = [&] {
321 if (dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
322 const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
323 const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
324 ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
325 : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
326 return string(convertedStringData.first.get(), convertedStringData.second);
327 } else { // Latin-1 or UTF-8
328 return stringFromSubstring(substr);
329 }
330 }();
331 value->assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
332 } catch (const ConversionException &) {
333 diag.emplace_back(DiagLevel::Warning, "The value of the length frame is not numeric and will be ignored.", context);
334 }
335
336 } else if ((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
337 // parse genre/content type
338 const auto genreIndex = [&] {
339 if (characterSize(dataEncoding) > 1) {
340 return parseGenreIndex(wideStringFromSubstring(substr, dataEncoding));
341 } else {
342 return parseGenreIndex(stringFromSubstring(substr));
343 }
344 }();
345 if (genreIndex != -1) {
346 // genre is specified as ID3 genre number
347 value->assignStandardGenreIndex(genreIndex);
348 } else {
349 // genre is specified as string
350 value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
351 }
352 } else {
353 // store any other text frames as-is
354 value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
355 }
356
357 currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
358 currentOffset = get<2>(substr);
359 }
360
361 // add warning about additional values
362 if (version < 4 && !m_additionalValues.empty()) {
363 diag.emplace_back(
364 DiagLevel::Warning, "Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
365 }
366
367 } else if (version >= 3 && id() == Id3v2FrameIds::lCover) {
368 // parse picture frame
369 std::uint8_t type;
370 parsePicture(buffer.get(), m_dataSize, value(), type, diag);
371 setTypeInfo(type);
372
373 } else if (version < 3 && id() == Id3v2FrameIds::sCover) {
374 // parse legacy picutre
375 std::uint8_t type;
376 parseLegacyPicture(buffer.get(), m_dataSize, value(), type, diag);
377 setTypeInfo(type);
378
379 } else if (((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
381 // parse comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
382 parseComment(buffer.get(), m_dataSize, value(), diag);
383
384 } else if (((version >= 3 && id() == Id3v2FrameIds::lPlayCounter) || (version < 3 && id() == Id3v2FrameIds::sPlayCounter))) {
385 // parse play counter frame
386 value().assignUnsignedInteger(readPlayCounter(buffer.get(), buffer.get() + m_dataSize, context, diag));
387
388 } else if (((version >= 3 && id() == Id3v2FrameIds::lRating) || (version < 3 && id() == Id3v2FrameIds::sRating))) {
389 // parse popularimeter frame
390 auto popularity = Popularity{ .scale = TagType::Id3v2Tag };
391 auto userEncoding = TagTextEncoding::Latin1;
392 auto substr = parseSubstring(buffer.get(), m_dataSize, userEncoding, true, diag);
393 auto end = buffer.get() + m_dataSize;
394 if (std::get<1>(substr)) {
395 popularity.user.assign(std::get<0>(substr), std::get<1>(substr));
396 }
397 auto ratingPos = std::get<2>(substr);
398 if (ratingPos >= end) {
399 diag.emplace_back(DiagLevel::Critical, "Popularimeter frame is incomplete (rating is missing).", context);
401 }
402 popularity.rating = static_cast<std::uint8_t>(*ratingPos);
403 popularity.playCounter = readPlayCounter(ratingPos + 1, end, context, diag);
404 value().assignPopularity(popularity);
405
406 } else {
407 // parse unknown/unsupported frame
408 value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
409 }
410}
411
423{
424 return Id3v2FrameMaker(*this, version, diag);
425}
426
435void Id3v2Frame::make(BinaryWriter &writer, std::uint8_t version, Diagnostics &diag)
436{
437 prepareMaking(version, diag).make(writer);
438}
439
443void Id3v2Frame::internallyClearValue()
444{
446 m_additionalValues.clear();
447}
448
452void Id3v2Frame::internallyClearFurtherData()
453{
454 m_flag = 0;
455 m_group = 0;
456 m_parsedVersion = 0;
457 m_dataSize = 0;
458 m_totalSize = 0;
459 m_padding = false;
460}
461
465std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg() const
466{
467 if (m_additionalValues.size() == 1) {
468 return argsToString("Additional value \"", m_additionalValues.front().toString(TagTextEncoding::Utf8), "\" is supposed to be ignored.");
469 }
470 return argsToString("Additional values ", DiagMessage::formatList(TagValue::toStrings(m_additionalValues)), " are supposed to be ignored.");
471}
472
476static std::uint32_t computePlayCounterSize(std::uint64_t playCounter)
477{
478 auto res = 4u;
479 for (playCounter >>= 32; playCounter; playCounter >>= 8, ++res)
480 ; // additional bytes for play counter into account when it is > 0xFFFFFFFF
481 return res;
482}
483
488static void writePlayCounter(char *last, std::uint32_t playCounterSize, std::uint64_t playCounter)
489{
490 for (; playCounter || playCounterSize; playCounter >>= 8, --playCounterSize, --last) {
491 *last = static_cast<char>(playCounter & 0xFF);
492 }
493}
494
506Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t version, Diagnostics &diag)
507 : m_frame(frame)
508 , m_frameId(m_frame.id())
509 , m_version(version)
510{
511 const string context("making " % m_frame.idToString() + " frame");
512
513 // validate frame's configuration
514 if (m_frame.isEncrypted()) {
515 diag.emplace_back(DiagLevel::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
516 throw InvalidDataException();
517 }
518 if (m_frame.hasPaddingReached()) {
519 diag.emplace_back(DiagLevel::Critical, "Cannot make a frame which is marked as padding.", context);
520 throw InvalidDataException();
521 }
522 if (version < 3 && m_frame.isCompressed()) {
523 diag.emplace_back(DiagLevel::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
524 }
525 if (version < 3 && (m_frame.flag() || m_frame.group())) {
526 diag.emplace_back(DiagLevel::Warning,
527 "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
528 }
529
530 // get non-empty, assigned values
531 vector<const TagValue *> values;
532 values.reserve(1 + frame.additionalValues().size());
533 if (!frame.value().isEmpty()) {
534 values.emplace_back(&frame.value());
535 }
536 for (const auto &value : frame.additionalValues()) {
537 if (!value.isEmpty()) {
538 values.emplace_back(&value);
539 }
540 }
541
542 // validate assigned values
543 if (values.empty()) {
544 throw NoDataProvidedException();
545 // note: This is not really an issue because in the case we're not provided with any value here just means that the field
546 // is supposed to be removed. So don't add any diagnostic messages here.
547 }
548 const bool isTextFrame = Id3v2FrameIds::isTextFrame(m_frameId);
549 if (values.size() != 1) {
550 if (!isTextFrame) {
551 diag.emplace_back(DiagLevel::Critical, "Multiple values are not supported for non-text-frames.", context);
552 throw InvalidDataException();
553 } else if (version < 4) {
554 diag.emplace_back(
555 DiagLevel::Warning, "Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
556 }
557 }
558
559 // convert frame ID if necessary
560 if (version >= 3) {
561 if (Id3v2FrameIds::isShortId(m_frameId)) {
562 // try to convert the short frame ID to its long equivalent
563 if (!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
564 diag.emplace_back(DiagLevel::Critical,
565 "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.",
566 context);
567 throw InvalidDataException();
568 }
569 }
570 } else {
571 if (Id3v2FrameIds::isLongId(m_frameId)) {
572 // try to convert the long frame ID to its short equivalent
573 if (!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
574 diag.emplace_back(DiagLevel::Critical,
575 "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.",
576 context);
577 throw InvalidDataException();
578 }
579 }
580 }
581
582 // add a warning if we're writing the frame for an ID3v2 tag known not to support it
583 if (version <= 3 && Id3v2FrameIds::isOnlyId3v24Id(version < 3 ? Id3v2FrameIds::convertToLongId(m_frameId) : m_frameId)) {
584 diag.emplace_back(DiagLevel::Warning,
585 argsToString("The frame is only supported in ID3v2.4 and newer but version of the tag being written is ID3v2.", version,
586 ". The frame is written nevertheless but other tools might not be able to deal with it."),
587 context);
588 } else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(m_frameId)) {
589 diag.emplace_back(DiagLevel::Warning,
590 argsToString("The frame is only supported in ID3v2.3 and older but version of the tag being written is ID3v2.", version,
591 ". The frame is written nevertheless but other tools might not be able to deal with it."),
592 context);
593 }
594
595 // make actual data depending on the frame ID
596 try {
597 if (isTextFrame) {
598 // make text frame
599 vector<string> substrings;
600 substrings.reserve(1 + frame.additionalValues().size());
602
603 if ((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
604 || (version < 3 && (m_frameId == Id3v2FrameIds::sTrackPosition || m_frameId == Id3v2FrameIds::sDiskPosition))) {
605 // make track number or disk number frame
607 for (const auto *const value : values) {
608 // convert the position to string
609 substrings.emplace_back(value->toString(encoding));
610 // warn if value is no valid position (although we just store a string after all)
612 continue;
613 }
614 try {
616 } catch (const ConversionException &) {
617 diag.emplace_back(DiagLevel::Warning,
618 argsToString("The track/disk number \"", substrings.back(), "\" is not of the expected form, eg. \"4/10\"."), context);
619 }
620 }
621
622 } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lLength) || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
623 // make length frame
624 encoding = TagTextEncoding::Latin1;
625 for (const auto *const value : values) {
626 const auto duration(value->toTimeSpan());
627 if (duration.isNegative()) {
628 diag.emplace_back(DiagLevel::Critical, argsToString("Assigned duration \"", duration.toString(), "\" is negative."), context);
629 throw InvalidDataException();
630 }
631 substrings.emplace_back(numberToString(static_cast<std::uint64_t>(duration.totalMilliseconds())));
632 }
633
634 } else {
635 // make standard genre index and other text frames
636 // -> find text encoding suitable for all assigned values
637 for (const auto *const value : values) {
638 switch (encoding) {
640 switch (value->type()) {
642 encoding = TagTextEncoding::Latin1;
643 break;
644 default:
645 encoding = value->dataEncoding();
646 }
647 break;
649 switch (value->dataEncoding()) {
651 break;
652 default:
653 encoding = value->dataEncoding();
654 }
655 break;
656 default:;
657 }
658 }
659 if (version <= 3 && encoding == TagTextEncoding::Utf8) {
661 }
662 // -> format values
663 for (const auto *const value : values) {
665 && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre) || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
666 // make standard genere index
667 substrings.emplace_back(numberToString(value->toStandardGenreIndex()));
668
669 } else {
670 // make other text frame
671 substrings.emplace_back(value->toString(encoding));
672 }
673 }
674 }
675
676 // concatenate substrings using encoding specific byte order mark and termination
677 const auto terminationLength = (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) ? 2u : 1u;
678 const auto byteOrderMark = [&] {
679 switch (encoding) {
681 return string({ '\xFF', '\xFE' });
683 return string({ '\xFE', '\xFF' });
684 default:
685 return string();
686 }
687 }();
688 const auto concatenatedSubstrings = joinStrings(substrings, string(), false, byteOrderMark, string(terminationLength, '\0'));
689
690 // write text encoding byte and concatenated strings to data buffer
691 m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(1 + concatenatedSubstrings.size()));
692 m_data[0] = static_cast<char>(Id3v2Frame::makeTextEncodingByte(encoding));
693 concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
694
695 } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lCover) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
696 // make picture frame
697 m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version, diag);
698
699 } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lComment) || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
700 || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
701 || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
702 // make comment frame or the unsynchronized lyrics frame
703 m_frame.makeComment(m_data, m_decompressedSize, *values.front(), version, diag);
704
705 } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lPlayCounter) || (version < 3 && m_frameId == Id3v2FrameIds::sPlayCounter))) {
706 // make play counter frame
707 auto playCounter = std::uint64_t();
708 try {
709 playCounter = values.front()->toUnsignedInteger();
710 } catch (const ConversionException &) {
711 diag.emplace_back(DiagLevel::Warning,
712 argsToString("The play counter \"", values.front()->toDisplayString(), "\" is not an unsigned integer."), context);
713 }
714 m_decompressedSize = computePlayCounterSize(playCounter);
715 m_data = make_unique<char[]>(m_decompressedSize);
716 writePlayCounter(m_data.get() + m_decompressedSize - 1, m_decompressedSize, playCounter);
717
718 } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lRating) || (version < 3 && m_frameId == Id3v2FrameIds::sRating))) {
719 // make popularimeter frame
720 auto popularity = Popularity();
721 try {
722 popularity = values.front()->toPopularity();
723 } catch (const ConversionException &) {
724 diag.emplace_back(DiagLevel::Warning,
725 argsToString(
726 "The popularity \"", values.front()->toDisplayString(), "\" is not of the expected form, eg. \"user|rating|counter\"."),
727 context);
728 }
729 // -> clamp rating
730 if (popularity.rating > 0xFF) {
731 popularity.rating = 0xFF;
732 diag.emplace_back(DiagLevel::Warning, argsToString("The rating has been clamped to 255."), context);
733 } else if (popularity.rating < 0x00) {
734 popularity.rating = 0x00;
735 diag.emplace_back(DiagLevel::Warning, argsToString("The rating has been clamped to 0."), context);
736 }
737 // -> compute size: user name length + termination + rating byte
738 m_decompressedSize = static_cast<std::uint32_t>(popularity.user.size() + 2);
739 const auto playCounterSize = computePlayCounterSize(popularity.playCounter);
740 m_decompressedSize += playCounterSize;
741 // -> copy data into buffer
742 m_data = make_unique<char[]>(m_decompressedSize);
743 auto pos = popularity.user.size() + 1;
744 std::memcpy(m_data.get(), popularity.user.data(), pos);
745 m_data[pos] = static_cast<char>(popularity.rating);
746 writePlayCounter(m_data.get() + pos + playCounterSize, playCounterSize, popularity.playCounter);
747
748 } else {
749 // make unknown frame
750 const auto &value(*values.front());
752 diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds maximum size.", context);
753 throw InvalidDataException();
754 }
755 m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(value.dataSize()));
756 std::memcpy(m_data.get(), value.dataPointer(), m_decompressedSize);
757 }
758 } catch (const ConversionException &) {
759 try {
760 const auto valuesAsString = TagValue::toStrings(values);
761 diag.emplace_back(DiagLevel::Critical,
762 argsToString("Assigned value(s) \"", DiagMessage::formatList(valuesAsString), "\" can not be converted appropriately."), context);
763 } catch (const ConversionException &) {
764 diag.emplace_back(DiagLevel::Critical, "Assigned value(s) can not be converted appropriately.", context);
765 }
766 throw InvalidDataException();
767 }
768
769 // apply compression if frame should be compressed
770 if (version >= 3 && m_frame.isCompressed()) {
771 auto compressedSize = compressBound(m_decompressedSize);
772 auto compressedData = make_unique<char[]>(compressedSize);
773 switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
774 reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
775 case Z_MEM_ERROR:
776 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
777 throw InvalidDataException();
778 case Z_BUF_ERROR:
779 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
780 throw InvalidDataException();
781 case Z_OK:;
782 }
783 if (compressedSize > maxId3v2FrameDataSize) {
784 diag.emplace_back(DiagLevel::Critical, "Compressed size exceeds maximum data size.", context);
785 throw InvalidDataException();
786 }
787 m_data.swap(compressedData);
788 m_dataSize = static_cast<std::uint32_t>(compressedSize);
789 } else {
790 m_dataSize = m_decompressedSize;
791 }
792
793 // calculate required size
794 // -> data size
795 m_requiredSize = m_dataSize;
796 if (version < 3) {
797 // -> header size
798 m_requiredSize += 6;
799 } else {
800 // -> header size
801 m_requiredSize += 10;
802 // -> group byte
803 if (m_frame.hasGroupInformation()) {
804 m_requiredSize += 1;
805 }
806 // -> decompressed size
807 if (version >= 3 && m_frame.isCompressed()) {
808 m_requiredSize += 4;
809 }
810 }
811}
812
820void Id3v2FrameMaker::make(BinaryWriter &writer)
821{
822 if (m_version < 3) {
823 writer.writeUInt24BE(m_frameId);
824 writer.writeUInt24BE(m_dataSize);
825 } else {
826 writer.writeUInt32BE(m_frameId);
827 if (m_version >= 4) {
828 writer.writeSynchsafeUInt32BE(m_dataSize);
829 } else {
830 writer.writeUInt32BE(m_dataSize);
831 }
832 writer.writeUInt16BE(m_frame.flag());
833 if (m_frame.hasGroupInformation()) {
834 writer.writeByte(m_frame.group());
835 }
836 if (m_version >= 3 && m_frame.isCompressed()) {
837 if (m_version >= 4) {
838 writer.writeSynchsafeUInt32BE(m_decompressedSize);
839 } else {
840 writer.writeUInt32BE(m_decompressedSize);
841 }
842 }
843 }
844 writer.write(m_data.get(), m_dataSize);
845}
846
854{
855 switch (textEncodingByte) {
856 case Id3v2TextEncodingBytes::Ascii:
858 case Id3v2TextEncodingBytes::Utf16WithBom:
860 case Id3v2TextEncodingBytes::Utf16BigEndianWithoutBom:
864 default:
865 diag.emplace_back(
866 DiagLevel::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + idToString());
868 }
869}
870
875{
876 switch (textEncoding) {
878 return Id3v2TextEncodingBytes::Ascii;
882 return Id3v2TextEncodingBytes::Utf16WithBom;
884 return Id3v2TextEncodingBytes::Utf16WithBom;
885 default:
886 return 0;
887 }
888}
889
904tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(
905 const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
906{
907 tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
908 switch (encoding) {
912 if ((bufferSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
913 if (encoding == TagTextEncoding::Latin1) {
914 diag.emplace_back(DiagLevel::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
915 "parsing frame " + idToString());
916 encoding = TagTextEncoding::Utf8;
917 }
918 get<0>(res) += 3;
919 }
920 const char *pos = get<0>(res);
921 for (; *pos != 0x00; ++pos) {
922 if (pos < get<2>(res)) {
923 ++get<1>(res);
924 } else {
925 if (addWarnings) {
926 diag.emplace_back(
927 DiagLevel::Warning, "String in frame is not terminated properly.", "parsing termination of frame " + idToString());
928 }
929 break;
930 }
931 }
932 get<2>(res) = pos + 1;
933 break;
934 }
937 if (bufferSize >= 2) {
938 switch (LE::toUInt16(buffer)) {
939 case 0xFEFF:
940 if (encoding == TagTextEncoding::Utf16BigEndian) {
941 diag.emplace_back(DiagLevel::Critical,
942 "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
943 "parsing frame " + idToString());
945 }
946 get<0>(res) += 2;
947 break;
948 case 0xFFFE:
950 get<0>(res) += 2;
951 }
952 }
953 const std::uint16_t *pos = reinterpret_cast<const std::uint16_t *>(get<0>(res));
954 for (; *pos != 0x0000; ++pos) {
955 if (pos < reinterpret_cast<const std::uint16_t *>(get<2>(res))) {
956 get<1>(res) += 2;
957 } else {
958 if (addWarnings) {
959 diag.emplace_back(
960 DiagLevel::Warning, "Wide string in frame is not terminated properly.", "parsing termination of frame " + idToString());
961 }
962 break;
963 }
964 }
965 get<2>(res) = reinterpret_cast<const char *>(pos + 1);
966 break;
967 }
968 }
969 return res;
970}
971
977string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
978{
979 return stringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag));
980}
981
989u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
990{
991 return wideStringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag), encoding);
992}
993
1001void Id3v2Frame::parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
1002{
1003 switch (encoding) {
1006 if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
1008 } else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
1010 }
1011 break;
1012 default:
1013 if ((maxSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
1014 encoding = TagTextEncoding::Utf8;
1015 diag.emplace_back(DiagLevel::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte order mark of frame " + idToString());
1016 }
1017 }
1018}
1019
1027void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
1028{
1029 static const string context("parsing ID3v2.2 picture frame");
1030 if (maxSize < 6) {
1031 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete.", context);
1032 throw TruncatedDataException();
1033 }
1034 const char *end = buffer + maxSize;
1035 auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
1036 typeInfo = static_cast<unsigned char>(*(buffer + 4));
1037 auto substr = parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding, true, diag);
1038 tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
1039 if (get<2>(substr) >= end) {
1040 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
1041 throw TruncatedDataException();
1042 }
1043 tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
1044}
1045
1053void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
1054{
1055 static const string context("parsing ID3v2.3 picture frame");
1056 const char *end = buffer + maxSize;
1057 auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
1058 auto mimeTypeEncoding = TagTextEncoding::Latin1;
1059 auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true, diag);
1060 if (get<1>(substr)) {
1061 tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
1062 }
1063 if (get<2>(substr) >= end) {
1064 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
1065 throw TruncatedDataException();
1066 }
1067 typeInfo = static_cast<unsigned char>(*get<2>(substr));
1068 if (++get<2>(substr) >= end) {
1069 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
1070 throw TruncatedDataException();
1071 }
1072 substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, true, diag);
1073 tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
1074 if (get<2>(substr) >= end) {
1075 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
1076 throw TruncatedDataException();
1077 }
1078 tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
1079}
1080
1087void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue, Diagnostics &diag)
1088{
1089 static const string context("parsing comment/unsynchronized lyrics frame");
1090 const char *end = buffer + dataSize;
1091 if (dataSize < 5) {
1092 diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete.", context);
1093 throw TruncatedDataException();
1094 }
1095 TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag);
1096 if (*(++buffer)) {
1097 tagValue.setLocale(Locale(std::string(buffer, 3), LocaleFormat::ISO_639_2_B)); // does standard say whether T or B?
1098 }
1099 auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true, diag);
1100 tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
1101 if (get<2>(substr) > end) {
1102 diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete (description not terminated?).", context);
1103 throw TruncatedDataException();
1104 }
1105 substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, false, diag);
1106 tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
1107}
1108
1113size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
1114{
1115 switch (encoding) {
1117 LE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1118 return 2;
1120 BE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1121 return 2;
1122 default:
1123 return 0;
1124 }
1125}
1126
1131 unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, Diagnostics &diag)
1132{
1133 // determine description
1134 TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1135 StringData convertedDescription;
1136 string::size_type descriptionSize = picture.description().find(
1137 "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1138 if (descriptionSize == string::npos) {
1139 descriptionSize = picture.description().size();
1140 }
1141 if (descriptionEncoding == TagTextEncoding::Utf8) {
1142 // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1143 descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1144 convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1145 descriptionSize = convertedDescription.second;
1146 }
1147
1148 // calculate needed buffer size and create buffer
1149 // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
1150 const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1151 + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1152 + picture.dataSize();
1153 if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
1154 diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making legacy picture frame");
1155 throw InvalidDataException();
1156 }
1157 buffer = make_unique<char[]>(bufferSize = static_cast<std::uint32_t>(requiredBufferSize));
1158 char *offset = buffer.get();
1159
1160 // write encoding byte
1161 *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1162
1163 // write mime type
1164 const char *imageFormat;
1165 if (picture.mimeType() == "image/jpeg") {
1166 imageFormat = "JPG";
1167 } else if (picture.mimeType() == "image/png") {
1168 imageFormat = "PNG";
1169 } else if (picture.mimeType() == "image/gif") {
1170 imageFormat = "GIF";
1171 } else if (picture.mimeType() == "-->") {
1172 imageFormat = picture.mimeType().data();
1173 } else {
1174 imageFormat = "UND";
1175 }
1176 strncpy(++offset, imageFormat, 3);
1177
1178 // write picture type
1179 *(offset += 3) = static_cast<char>(typeInfo);
1180
1181 // write description
1182 offset += makeBom(offset + 1, descriptionEncoding);
1183 if (convertedDescription.first) {
1184 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1185 } else {
1186 picture.description().copy(++offset, descriptionSize);
1187 }
1188 *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1189 if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1190 *(++offset) = 0x00;
1191 }
1192
1193 // write actual data
1194 copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1195}
1196
1200void Id3v2Frame::makePicture(std::unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo,
1201 std::uint8_t version, Diagnostics &diag)
1202{
1203 if (version < 3) {
1204 makeLegacyPicture(buffer, bufferSize, picture, typeInfo, diag);
1205 return;
1206 }
1207
1208 // determine description
1209 TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1210 StringData convertedDescription;
1211 string::size_type descriptionSize = picture.description().find(
1212 "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1213 if (descriptionSize == string::npos) {
1214 descriptionSize = picture.description().size();
1215 }
1216 if (version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
1217 // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1218 descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1219 convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1220 descriptionSize = convertedDescription.second;
1221 }
1222 // determine mime-type
1223 string::size_type mimeTypeSize = picture.mimeType().find('\0');
1224 if (mimeTypeSize == string::npos) {
1225 mimeTypeSize = picture.mimeType().length();
1226 }
1227
1228 // calculate needed buffer size and create buffer
1229 // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
1230 const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1231 + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1232 + picture.dataSize();
1233 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1234 diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making picture frame");
1235 throw InvalidDataException();
1236 }
1237 buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1238 char *offset = buffer.get();
1239
1240 // write encoding byte
1241 *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1242
1243 // write mime type
1244 picture.mimeType().copy(++offset, mimeTypeSize);
1245
1246 *(offset += mimeTypeSize) = 0x00; // terminate mime type
1247 // write picture type
1248 *(++offset) = static_cast<char>(typeInfo);
1249
1250 // write description
1251 offset += makeBom(offset + 1, descriptionEncoding);
1252 if (convertedDescription.first) {
1253 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1254 } else {
1255 picture.description().copy(++offset, descriptionSize);
1256 }
1257 *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1258 if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1259 *(++offset) = 0x00;
1260 }
1261
1262 // write actual data
1263 copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1264}
1265
1269void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &comment, std::uint8_t version, Diagnostics &diag)
1270{
1271 static const string context("making comment frame");
1272
1273 // check whether type and other values are valid
1274 TagTextEncoding encoding = comment.dataEncoding();
1275 if (!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1276 diag.emplace_back(DiagLevel::Critical, "Data encoding and description encoding aren't equal.", context);
1277 throw InvalidDataException();
1278 }
1280 if (language.length() > 3) {
1281 diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1282 throw InvalidDataException();
1283 }
1284 StringData convertedDescription;
1285 string::size_type descriptionSize = comment.description().find(
1286 "\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1287 if (descriptionSize == string::npos) {
1288 descriptionSize = comment.description().size();
1289 }
1290 if (version < 4 && encoding == TagTextEncoding::Utf8) {
1291 // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1293 convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1294 descriptionSize = convertedDescription.second;
1295 }
1296
1297 // calculate needed buffer size and create buffer
1298 // note: encoding byte + language + description size + actual data size + BOMs and termination
1299 const auto data = comment.toString(encoding);
1300 const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
1301 + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size();
1302 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1303 diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", context);
1304 throw InvalidDataException();
1305 }
1306 buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1307 char *offset = buffer.get();
1308
1309 // write encoding
1310 *offset = static_cast<char>(makeTextEncodingByte(encoding));
1311
1312 // write language
1313 for (unsigned int i = 0; i < 3; ++i) {
1314 *(++offset) = (language.length() > i) ? language[i] : 0x00;
1315 }
1316
1317 // write description
1318 offset += makeBom(offset + 1, encoding);
1319 if (convertedDescription.first) {
1320 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1321 } else {
1322 comment.description().copy(++offset, descriptionSize);
1323 }
1324 offset += descriptionSize;
1325 *offset = 0x00; // terminate description and increase data offset
1327 *(++offset) = 0x00;
1328 }
1329
1330 // write actual data
1331 offset += makeBom(offset + 1, encoding);
1332 data.copy(++offset, data.size());
1333}
1334
1335} // 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:820
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:874
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:977
void parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
Parses a byte order mark from the specified buffer.
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.
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.
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:152
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:904
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:41
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:853
Id3v2FrameMaker prepareMaking(std::uint8_t version, Diagnostics &diag)
Prepares making.
Definition: id3v2frame.cpp:422
void parseComment(const char *buffer, std::size_t maxSize, TagValue &tagValue, Diagnostics &diag)
Parses the comment/unsynchronized lyrics from the specified buffer.
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:989
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:435
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:130
void setMimeType(std::string_view mimeType)
Sets the MIME type.
Definition: tagvalue.h:696
const std::string & mimeType() const
Returns the MIME type.
Definition: tagvalue.h:684
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:1128
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:812
void assignPosition(PositionInSet value)
Assigns the given PositionInSet value.
Definition: tagvalue.h:487
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:671
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
Definition: tagvalue.cpp:506
TagDataType type() const
Returns the type of the assigned value.
Definition: tagvalue.h:527
void assignPopularity(const Popularity &value)
Assigns the specified popularity value.
Definition: tagvalue.cpp:1077
void assignTimeSpan(CppUtilities::TimeSpan value)
Assigns the given TimeSpan value.
Definition: tagvalue.h:500
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:835
void assignUnsignedInteger(std::uint64_t value)
Assigns the given unsigned integer value.
Definition: tagvalue.cpp:1019
void clearDataAndMetadata()
Wipes assigned data including meta data.
Definition: tagvalue.h:606
std::size_t dataSize() const
Returns the size of the assigned value in bytes.
Definition: tagvalue.h:616
TagTextEncoding descriptionEncoding() const
Returns the description encoding.
Definition: tagvalue.h:822
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.h:518
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:544
void setLocale(const Locale &locale)
Sets the setLocale.
Definition: tagvalue.h:738
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:584
const std::string & description() const
Returns the description.
Definition: tagvalue.h:655
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
Definition: tagvalue.cpp:456
CppUtilities::TimeSpan toTimeSpan() const
Converts the value of the current TagValue object to its equivalent TimeSpan representation.
Definition: tagvalue.cpp:555
char * dataPointer()
Returns a pointer to the raw data assigned to the current instance.
Definition: tagvalue.h:627
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:95
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 playCounter()
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:58
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:29
The Locale struct specifies a language and/or a country using one or more LocaleDetail objects.
Definition: localehelper.h:61
TagType scale
Specifies the scale used for rating by the tag defining that scale.
Definition: tagvalue.h:83