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