Tag Parser 12.4.0
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 const auto isTextFrame = Id3v2FrameIds::isTextFrame(id());
273 const auto isUrlFrame = Id3v2FrameIds::isUrlFrame(id());
274 // read tag value depending on frame ID/type
275 if (isTextFrame || isUrlFrame) {
276 // parse text encoding byte
277 const char *currentOffset = buffer.get();
278 auto dataEncoding = isTextFrame ? parseTextEncodingByte(static_cast<std::uint8_t>(*(currentOffset++)), diag) : TagTextEncoding::Latin1;
279
280 // parse string values (since ID3v2.4 a text frame may contain multiple strings)
281 for (size_t currentIndex = 1; currentIndex < m_dataSize;) {
282 // determine the next substring
283 const auto substr(parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding, false, diag));
284
285 // handle case when string is empty
286 if (!get<1>(substr)) {
287 if (currentIndex == 1) {
289 }
290 currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
291 currentOffset = get<2>(substr);
292 continue;
293 }
294
295 // determine the TagValue instance to store the value
296 TagValue *const value = [&] {
297 if (this->value().isEmpty()) {
298 return &this->value();
299 }
300 m_additionalValues.emplace_back();
301 return &m_additionalValues.back();
302 }();
303
304 // apply further parsing for some text frame types (eg. convert track number to PositionInSet)
305 if ((version >= 3 && (id() == Id3v2FrameIds::lTrackPosition || id() == Id3v2FrameIds::lDiskPosition))
306 || (version < 3 && (id() == Id3v2FrameIds::sTrackPosition || id() == Id3v2FrameIds::sDiskPosition))) {
307 // parse the track number or the disk number frame
308 try {
309 if (characterSize(dataEncoding) > 1) {
310 value->assignPosition(PositionInSet(wideStringFromSubstring(substr, dataEncoding)));
311 } else {
312 value->assignPosition(PositionInSet(stringFromSubstring(substr)));
313 }
314 } catch (const ConversionException &) {
315 diag.emplace_back(DiagLevel::Warning, "The value of track/disk position frame is not numeric and will be ignored.", context);
316 }
317
318 } else if ((version >= 3 && id() == Id3v2FrameIds::lLength) || (version < 3 && id() == Id3v2FrameIds::sLength)) {
319 // parse frame contains length
320 try {
321 const auto milliseconds = [&] {
322 if (dataEncoding == TagTextEncoding::Utf16BigEndian || dataEncoding == TagTextEncoding::Utf16LittleEndian) {
323 const auto parsedStringRef = parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding, false, diag);
324 const auto convertedStringData = dataEncoding == TagTextEncoding::Utf16BigEndian
325 ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
326 : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
327 return string(convertedStringData.first.get(), convertedStringData.second);
328 } else { // Latin-1 or UTF-8
329 return stringFromSubstring(substr);
330 }
331 }();
332 value->assignTimeSpan(TimeSpan::fromMilliseconds(stringToNumber<double>(milliseconds)));
333 } catch (const ConversionException &) {
334 diag.emplace_back(DiagLevel::Warning, "The value of the length frame is not numeric and will be ignored.", context);
335 }
336
337 } else if ((version >= 3 && id() == Id3v2FrameIds::lGenre) || (version < 3 && id() == Id3v2FrameIds::sGenre)) {
338 // parse genre/content type
339 const auto genreIndex = [&] {
340 if (characterSize(dataEncoding) > 1) {
341 return parseGenreIndex(wideStringFromSubstring(substr, dataEncoding));
342 } else {
343 return parseGenreIndex(stringFromSubstring(substr));
344 }
345 }();
346 if (genreIndex != -1) {
347 // genre is specified as ID3 genre number
348 value->assignStandardGenreIndex(genreIndex);
349 } else {
350 // genre is specified as string
351 value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
352 }
353 } else {
354 // store any other text frames as-is
355 value->assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
356 }
357
358 currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
359 currentOffset = get<2>(substr);
360 }
361
362 // add warning about additional values
363 if (version < 4 && !m_additionalValues.empty()) {
364 diag.emplace_back(
365 DiagLevel::Warning, "Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
366 }
367
368 } else if (version >= 3 && id() == Id3v2FrameIds::lCover) {
369 // parse picture frame
370 std::uint8_t type;
371 parsePicture(buffer.get(), m_dataSize, value(), type, diag);
372 setTypeInfo(type);
373
374 } else if (version < 3 && id() == Id3v2FrameIds::sCover) {
375 // parse legacy picutre
376 std::uint8_t type;
377 parseLegacyPicture(buffer.get(), m_dataSize, value(), type, diag);
378 setTypeInfo(type);
379
380 } else if (((version >= 3 && id() == Id3v2FrameIds::lComment) || (version < 3 && id() == Id3v2FrameIds::sComment))
381 || ((version >= 3 && id() == Id3v2FrameIds::lUnsynchronizedLyrics) || (version < 3 && id() == Id3v2FrameIds::sUnsynchronizedLyrics))) {
382 // parse comment frame or unsynchronized lyrics frame (these two frame types have the same structure)
383 parseComment(buffer.get(), m_dataSize, value(), diag);
384
385 } else if (((version >= 3 && id() == Id3v2FrameIds::lPlayCounter) || (version < 3 && id() == Id3v2FrameIds::sPlayCounter))) {
386 // parse play counter frame
387 value().assignUnsignedInteger(readPlayCounter(buffer.get(), buffer.get() + m_dataSize, context, diag));
388
389 } else if (((version >= 3 && id() == Id3v2FrameIds::lRating) || (version < 3 && id() == Id3v2FrameIds::sRating))) {
390 // parse popularimeter frame
391 auto popularity = Popularity{ .scale = TagType::Id3v2Tag };
392 auto userEncoding = TagTextEncoding::Latin1;
393 auto substr = parseSubstring(buffer.get(), m_dataSize, userEncoding, true, diag);
394 auto end = buffer.get() + m_dataSize;
395 if (std::get<1>(substr)) {
396 popularity.user.assign(std::get<0>(substr), std::get<1>(substr));
397 }
398 auto ratingPos = std::get<2>(substr);
399 if (ratingPos >= end) {
400 diag.emplace_back(DiagLevel::Critical, "Popularimeter frame is incomplete (rating is missing).", context);
402 }
403 popularity.rating = static_cast<std::uint8_t>(*ratingPos);
404 popularity.playCounter = readPlayCounter(ratingPos + 1, end, context, diag);
405 value().assignPopularity(popularity);
406
407 } else {
408 // parse unknown/unsupported frame
409 value().assignData(buffer.get(), m_dataSize, TagDataType::Undefined);
410 }
411}
412
424{
425 return Id3v2FrameMaker(*this, version, diag);
426}
427
436void Id3v2Frame::make(BinaryWriter &writer, std::uint8_t version, Diagnostics &diag)
437{
438 prepareMaking(version, diag).make(writer);
439}
440
444void Id3v2Frame::internallyClearValue()
445{
447 m_additionalValues.clear();
448}
449
453void Id3v2Frame::internallyClearFurtherData()
454{
455 m_flag = 0;
456 m_group = 0;
457 m_parsedVersion = 0;
458 m_dataSize = 0;
459 m_totalSize = 0;
460 m_padding = false;
461}
462
466std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg() const
467{
468 if (m_additionalValues.size() == 1) {
469 return argsToString("Additional value \"", m_additionalValues.front().toString(TagTextEncoding::Utf8), "\" is supposed to be ignored.");
470 }
471 return argsToString("Additional values ", DiagMessage::formatList(TagValue::toStrings(m_additionalValues)), " are supposed to be ignored.");
472}
473
477static std::uint32_t computePlayCounterSize(std::uint64_t playCounter)
478{
479 auto res = 4u;
480 for (playCounter >>= 32; playCounter; playCounter >>= 8, ++res)
481 ; // additional bytes for play counter into account when it is > 0xFFFFFFFF
482 return res;
483}
484
489static void writePlayCounter(char *last, std::uint32_t playCounterSize, std::uint64_t playCounter)
490{
491 for (; playCounter || playCounterSize; playCounter >>= 8, --playCounterSize, --last) {
492 *last = static_cast<char>(playCounter & 0xFF);
493 }
494}
495
507Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t version, Diagnostics &diag)
508 : m_frame(frame)
509 , m_frameId(m_frame.id())
510 , m_version(version)
511{
512 const string context("making " % m_frame.idToString() + " frame");
513
514 // validate frame's configuration
515 if (m_frame.isEncrypted()) {
516 diag.emplace_back(DiagLevel::Critical, "Cannot make an encrypted frame (isn't supported by this tagging library).", context);
517 throw InvalidDataException();
518 }
519 if (m_frame.hasPaddingReached()) {
520 diag.emplace_back(DiagLevel::Critical, "Cannot make a frame which is marked as padding.", context);
521 throw InvalidDataException();
522 }
523 if (version < 3 && m_frame.isCompressed()) {
524 diag.emplace_back(DiagLevel::Warning, "Compression is not supported by the version of ID3v2 and won't be applied.", context);
525 }
526 if (version < 3 && (m_frame.flag() || m_frame.group())) {
527 diag.emplace_back(DiagLevel::Warning,
528 "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
529 }
530
531 // get non-empty, assigned values
532 auto values = std::vector<const TagValue *>();
533 values.reserve(1 + frame.additionalValues().size());
534 if (!frame.value().isEmpty()) {
535 values.emplace_back(&frame.value());
536 }
537 for (const auto &value : frame.additionalValues()) {
538 if (!value.isEmpty()) {
539 values.emplace_back(&value);
540 }
541 }
542
543 // validate assigned values
544 if (values.empty()) {
545 throw NoDataProvidedException();
546 // note: This is not really an issue because in the case we're not provided with any value here just means that the field
547 // is supposed to be removed. So don't add any diagnostic messages here.
548 }
549 const auto isTextFrame = Id3v2FrameIds::isTextFrame(m_frameId);
550 const auto isUrlFrame = Id3v2FrameIds::isUrlFrame(m_frameId);
551 if (values.size() != 1) {
552 if (!isTextFrame) {
553 diag.emplace_back(DiagLevel::Critical, "Multiple values are not supported for non-text-frames.", context);
554 throw InvalidDataException();
555 } else if (version < 4) {
556 diag.emplace_back(
557 DiagLevel::Warning, "Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
558 }
559 }
560
561 // convert frame ID if necessary
562 if (version >= 3) {
563 if (Id3v2FrameIds::isShortId(m_frameId)) {
564 // try to convert the short frame ID to its long equivalent
565 if (!(m_frameId = Id3v2FrameIds::convertToLongId(m_frameId))) {
566 diag.emplace_back(DiagLevel::Critical,
567 "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.",
568 context);
569 throw InvalidDataException();
570 }
571 }
572 } else {
573 if (Id3v2FrameIds::isLongId(m_frameId)) {
574 // try to convert the long frame ID to its short equivalent
575 if (!(m_frameId = Id3v2FrameIds::convertToShortId(m_frameId))) {
576 diag.emplace_back(DiagLevel::Critical,
577 "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.",
578 context);
579 throw InvalidDataException();
580 }
581 }
582 }
583
584 // add a warning if we're writing the frame for an ID3v2 tag known not to support it
585 if (version <= 3 && Id3v2FrameIds::isOnlyId3v24Id(version < 3 ? Id3v2FrameIds::convertToLongId(m_frameId) : m_frameId)) {
586 diag.emplace_back(DiagLevel::Warning,
587 argsToString("The frame is only supported in ID3v2.4 and newer but version of the tag being written is ID3v2.", version,
588 ". The frame is written nevertheless but other tools might not be able to deal with it."),
589 context);
590 } else if (version > 3 && Id3v2FrameIds::isPreId3v24Id(m_frameId)) {
591 diag.emplace_back(DiagLevel::Warning,
592 argsToString("The frame is only supported in ID3v2.3 and older but version of the tag being written is ID3v2.", version,
593 ". The frame is written nevertheless but other tools might not be able to deal with it."),
594 context);
595 }
596
597 // make actual data depending on the frame ID
598 try {
599 if (isTextFrame || isUrlFrame) {
600 // make text frame
601 auto substrings = std::vector<std::string>();
602 substrings.reserve(1 + frame.additionalValues().size());
603 auto encoding = TagTextEncoding::Unspecified;
604
605 if ((version >= 3 && (m_frameId == Id3v2FrameIds::lTrackPosition || m_frameId == Id3v2FrameIds::lDiskPosition))
606 || (version < 3 && (m_frameId == Id3v2FrameIds::sTrackPosition || m_frameId == Id3v2FrameIds::sDiskPosition))) {
607 // make track number or disk number frame
609 for (const auto *const value : values) {
610 // convert the position to string
611 substrings.emplace_back(value->toString(encoding));
612 // warn if value is no valid position (although we just store a string after all)
614 continue;
615 }
616 try {
618 } catch (const ConversionException &) {
619 diag.emplace_back(DiagLevel::Warning,
620 argsToString("The track/disk number \"", substrings.back(), "\" is not of the expected form, eg. \"4/10\"."), context);
621 }
622 }
623
624 } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lLength) || (version < 3 && m_frameId == Id3v2FrameIds::sLength)) {
625 // make length frame
626 encoding = TagTextEncoding::Latin1;
627 for (const auto *const value : values) {
628 const auto duration(value->toTimeSpan());
629 if (duration.isNegative()) {
630 diag.emplace_back(DiagLevel::Critical, argsToString("Assigned duration \"", duration.toString(), "\" is negative."), context);
631 throw InvalidDataException();
632 }
633 substrings.emplace_back(numberToString(static_cast<std::uint64_t>(duration.totalMilliseconds())));
634 }
635
636 } else if (isUrlFrame) {
637 // make URL link frame
638 encoding = TagTextEncoding::Latin1;
639 for (const auto *const value : values) {
640 substrings.emplace_back(value->toString(encoding));
641 }
642
643 } else {
644 // make standard genre index and other text frames
645 // -> find text encoding suitable for all assigned values
646 for (const auto *const value : values) {
647 switch (encoding) {
649 switch (value->type()) {
651 encoding = TagTextEncoding::Latin1;
652 break;
653 default:
654 encoding = value->dataEncoding();
655 }
656 break;
658 switch (value->dataEncoding()) {
660 break;
661 default:
662 encoding = value->dataEncoding();
663 }
664 break;
665 default:;
666 }
667 }
668 if (version <= 3 && encoding == TagTextEncoding::Utf8) {
670 }
671 // -> format values
672 for (const auto *const value : values) {
674 && ((version >= 3 && m_frameId == Id3v2FrameIds::lGenre) || (version < 3 && m_frameId == Id3v2FrameIds::sGenre))) {
675 // make standard genere index
676 substrings.emplace_back(numberToString(value->toStandardGenreIndex()));
677
678 } else {
679 // make other text frame
680 substrings.emplace_back(value->toString(encoding));
681 }
682 }
683 }
684
685 // concatenate substrings using encoding specific byte order mark and termination
686 const auto terminationLength = (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian) ? 2u : 1u;
687 const auto byteOrderMark = [&] {
688 switch (encoding) {
690 return std::string({ '\xFF', '\xFE' });
692 return std::string({ '\xFE', '\xFF' });
693 default:
694 return std::string();
695 }
696 }();
697 const auto concatenatedSubstrings = joinStrings(substrings, std::string(), false, byteOrderMark, std::string(terminationLength, '\0'));
698
699 // write text encoding byte and concatenated strings to data buffer
700 if (isTextFrame) {
701 m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(1 + concatenatedSubstrings.size()));
702 m_data[0] = static_cast<char>(Id3v2Frame::makeTextEncodingByte(encoding));
703 concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
704 } else {
705 m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(concatenatedSubstrings.size()));
706 concatenatedSubstrings.copy(m_data.get(), concatenatedSubstrings.size());
707 }
708
709 } else if ((version >= 3 && m_frameId == Id3v2FrameIds::lCover) || (version < 3 && m_frameId == Id3v2FrameIds::sCover)) {
710 // make picture frame
711 m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0, version, diag);
712
713 } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lComment) || (version < 3 && m_frameId == Id3v2FrameIds::sComment))
714 || ((version >= 3 && m_frameId == Id3v2FrameIds::lUnsynchronizedLyrics)
715 || (version < 3 && m_frameId == Id3v2FrameIds::sUnsynchronizedLyrics))) {
716 // make comment frame or the unsynchronized lyrics frame
717 m_frame.makeComment(m_data, m_decompressedSize, *values.front(), version, diag);
718
719 } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lPlayCounter) || (version < 3 && m_frameId == Id3v2FrameIds::sPlayCounter))) {
720 // make play counter frame
721 auto playCounter = std::uint64_t();
722 try {
723 playCounter = values.front()->toUnsignedInteger();
724 } catch (const ConversionException &) {
725 diag.emplace_back(DiagLevel::Warning,
726 argsToString("The play counter \"", values.front()->toDisplayString(), "\" is not an unsigned integer."), context);
727 }
728 m_decompressedSize = computePlayCounterSize(playCounter);
729 m_data = make_unique<char[]>(m_decompressedSize);
730 writePlayCounter(m_data.get() + m_decompressedSize - 1, m_decompressedSize, playCounter);
731
732 } else if (((version >= 3 && m_frameId == Id3v2FrameIds::lRating) || (version < 3 && m_frameId == Id3v2FrameIds::sRating))) {
733 // make popularimeter frame
734 auto popularity = Popularity();
735 try {
736 popularity = values.front()->toScaledPopularity(TagType::Id3v2Tag);
737 } catch (const ConversionException &) {
738 diag.emplace_back(DiagLevel::Warning,
739 argsToString(
740 "The popularity \"", values.front()->toDisplayString(), "\" is not of the expected form, eg. \"user|rating|counter\"."),
741 context);
742 }
743 // -> clamp rating
744 if (popularity.rating > 0xFF) {
745 popularity.rating = 0xFF;
746 diag.emplace_back(DiagLevel::Warning, argsToString("The rating has been clamped to 255."), context);
747 } else if (popularity.rating < 0x00) {
748 popularity.rating = 0x00;
749 diag.emplace_back(DiagLevel::Warning, argsToString("The rating has been clamped to 0."), context);
750 }
751 // -> compute size: user name length + termination + rating byte
752 m_decompressedSize = static_cast<std::uint32_t>(popularity.user.size() + 2);
753 const auto playCounterSize = computePlayCounterSize(popularity.playCounter);
754 m_decompressedSize += playCounterSize;
755 // -> copy data into buffer
756 m_data = make_unique<char[]>(m_decompressedSize);
757 auto pos = popularity.user.size() + 1;
758 std::memcpy(m_data.get(), popularity.user.data(), pos);
759 m_data[pos] = static_cast<char>(popularity.rating);
760 writePlayCounter(m_data.get() + pos + playCounterSize, playCounterSize, popularity.playCounter);
761
762 } else {
763 // make unknown frame
764 const auto &value(*values.front());
766 diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds maximum size.", context);
767 throw InvalidDataException();
768 }
769 m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(value.dataSize()));
770 std::memcpy(m_data.get(), value.dataPointer(), m_decompressedSize);
771 }
772 } catch (const ConversionException &) {
773 try {
774 const auto valuesAsString = TagValue::toStrings(values);
775 diag.emplace_back(DiagLevel::Critical,
776 argsToString("Assigned value(s) \"", DiagMessage::formatList(valuesAsString), "\" can not be converted appropriately."), context);
777 } catch (const ConversionException &) {
778 diag.emplace_back(DiagLevel::Critical, "Assigned value(s) can not be converted appropriately.", context);
779 }
780 throw InvalidDataException();
781 }
782
783 // apply compression if frame should be compressed
784 if (version >= 3 && m_frame.isCompressed()) {
785 auto compressedSize = compressBound(m_decompressedSize);
786 auto compressedData = make_unique<char[]>(compressedSize);
787 switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
788 reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
789 case Z_MEM_ERROR:
790 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The source buffer was too small.", context);
791 throw InvalidDataException();
792 case Z_BUF_ERROR:
793 diag.emplace_back(DiagLevel::Critical, "Decompressing failed. The destination buffer was too small.", context);
794 throw InvalidDataException();
795 case Z_OK:;
796 }
797 if (compressedSize > maxId3v2FrameDataSize) {
798 diag.emplace_back(DiagLevel::Critical, "Compressed size exceeds maximum data size.", context);
799 throw InvalidDataException();
800 }
801 m_data.swap(compressedData);
802 m_dataSize = static_cast<std::uint32_t>(compressedSize);
803 } else {
804 m_dataSize = m_decompressedSize;
805 }
806
807 // calculate required size
808 // -> data size
809 m_requiredSize = m_dataSize;
810 if (version < 3) {
811 // -> header size
812 m_requiredSize += 6;
813 } else {
814 // -> header size
815 m_requiredSize += 10;
816 // -> group byte
817 if (m_frame.hasGroupInformation()) {
818 m_requiredSize += 1;
819 }
820 // -> decompressed size
821 if (version >= 3 && m_frame.isCompressed()) {
822 m_requiredSize += 4;
823 }
824 }
825}
826
834void Id3v2FrameMaker::make(BinaryWriter &writer)
835{
836 if (m_version < 3) {
837 writer.writeUInt24BE(m_frameId);
838 writer.writeUInt24BE(m_dataSize);
839 } else {
840 writer.writeUInt32BE(m_frameId);
841 if (m_version >= 4) {
842 writer.writeSynchsafeUInt32BE(m_dataSize);
843 } else {
844 writer.writeUInt32BE(m_dataSize);
845 }
846 writer.writeUInt16BE(m_frame.flag());
847 if (m_frame.hasGroupInformation()) {
848 writer.writeByte(m_frame.group());
849 }
850 if (m_version >= 3 && m_frame.isCompressed()) {
851 if (m_version >= 4) {
852 writer.writeSynchsafeUInt32BE(m_decompressedSize);
853 } else {
854 writer.writeUInt32BE(m_decompressedSize);
855 }
856 }
857 }
858 writer.write(m_data.get(), m_dataSize);
859}
860
868{
869 switch (textEncodingByte) {
870 case Id3v2TextEncodingBytes::Ascii:
872 case Id3v2TextEncodingBytes::Utf16WithBom:
874 case Id3v2TextEncodingBytes::Utf16BigEndianWithoutBom:
876 case Id3v2TextEncodingBytes::Utf8:
878 default:
879 diag.emplace_back(
880 DiagLevel::Warning, "The charset of the frame is invalid. Latin-1 will be used.", "parsing encoding of frame " + idToString());
882 }
883}
884
889{
890 switch (textEncoding) {
892 return Id3v2TextEncodingBytes::Ascii;
894 return Id3v2TextEncodingBytes::Utf8;
896 return Id3v2TextEncodingBytes::Utf16WithBom;
898 return Id3v2TextEncodingBytes::Utf16WithBom;
899 default:
900 return 0;
901 }
902}
903
918tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(
919 const char *buffer, std::size_t bufferSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
920{
921 tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
922 switch (encoding) {
926 if ((bufferSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
927 if (encoding == TagTextEncoding::Latin1) {
928 diag.emplace_back(DiagLevel::Critical, "Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
929 "parsing frame " + idToString());
930 encoding = TagTextEncoding::Utf8;
931 }
932 get<0>(res) += 3;
933 }
934 const char *pos = get<0>(res);
935 for (; *pos != 0x00; ++pos) {
936 if (pos < get<2>(res)) {
937 ++get<1>(res);
938 } else {
939 if (addWarnings) {
940 diag.emplace_back(
941 DiagLevel::Warning, "String in frame is not terminated properly.", "parsing termination of frame " + idToString());
942 }
943 break;
944 }
945 }
946 get<2>(res) = pos + 1;
947 break;
948 }
951 if (bufferSize >= 2) {
952 switch (LE::toInt<std::uint16_t>(buffer)) {
953 case 0xFEFF:
954 if (encoding == TagTextEncoding::Utf16BigEndian) {
955 diag.emplace_back(DiagLevel::Critical,
956 "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
957 "parsing frame " + idToString());
959 }
960 get<0>(res) += 2;
961 break;
962 case 0xFFFE:
964 get<0>(res) += 2;
965 }
966 }
967 const std::uint16_t *pos = reinterpret_cast<const std::uint16_t *>(get<0>(res));
968 for (; *pos != 0x0000; ++pos) {
969 if (pos < reinterpret_cast<const std::uint16_t *>(get<2>(res))) {
970 get<1>(res) += 2;
971 } else {
972 if (addWarnings) {
973 diag.emplace_back(
974 DiagLevel::Warning, "Wide string in frame is not terminated properly.", "parsing termination of frame " + idToString());
975 }
976 break;
977 }
978 }
979 get<2>(res) = reinterpret_cast<const char *>(pos + 1);
980 break;
981 }
982 }
983 return res;
984}
985
991string Id3v2Frame::parseString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
992{
993 return stringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag));
994}
995
1003u16string Id3v2Frame::parseWideString(const char *buffer, size_t dataSize, TagTextEncoding &encoding, bool addWarnings, Diagnostics &diag)
1004{
1005 return wideStringFromSubstring(parseSubstring(buffer, dataSize, encoding, addWarnings, diag), encoding);
1006}
1007
1015void Id3v2Frame::parseBom(const char *buffer, std::size_t maxSize, TagTextEncoding &encoding, Diagnostics &diag)
1016{
1017 switch (encoding) {
1020 if ((maxSize >= 2) && (BE::toInt<std::uint16_t>(buffer) == 0xFFFE)) {
1022 } else if ((maxSize >= 2) && (BE::toInt<std::uint16_t>(buffer) == 0xFEFF)) {
1024 }
1025 break;
1026 default:
1027 if ((maxSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
1028 encoding = TagTextEncoding::Utf8;
1029 diag.emplace_back(DiagLevel::Warning, "UTF-8 byte order mark found in text frame.", "parsing byte order mark of frame " + idToString());
1030 }
1031 }
1032}
1033
1041void Id3v2Frame::parseLegacyPicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
1042{
1043 static const string context("parsing ID3v2.2 picture frame");
1044 if (maxSize < 6) {
1045 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete.", context);
1046 throw TruncatedDataException();
1047 }
1048 const char *end = buffer + maxSize;
1049 auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
1050 typeInfo = static_cast<unsigned char>(*(buffer + 4));
1051 auto substr = parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding, true, diag);
1052 tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
1053 if (get<2>(substr) >= end) {
1054 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
1055 throw TruncatedDataException();
1056 }
1057 tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
1058}
1059
1067void Id3v2Frame::parsePicture(const char *buffer, std::size_t maxSize, TagValue &tagValue, std::uint8_t &typeInfo, Diagnostics &diag)
1068{
1069 static const string context("parsing ID3v2.3 picture frame");
1070 const char *end = buffer + maxSize;
1071 auto dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag); // the first byte stores the encoding
1072 auto mimeTypeEncoding = TagTextEncoding::Latin1;
1073 auto substr = parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding, true, diag);
1074 if (get<1>(substr)) {
1075 tagValue.setMimeType(string(get<0>(substr), get<1>(substr)));
1076 }
1077 if (get<2>(substr) >= end) {
1078 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (type info, description and actual data are missing).", context);
1079 throw TruncatedDataException();
1080 }
1081 typeInfo = static_cast<unsigned char>(*get<2>(substr));
1082 if (++get<2>(substr) >= end) {
1083 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (description and actual data are missing).", context);
1084 throw TruncatedDataException();
1085 }
1086 substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, true, diag);
1087 tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
1088 if (get<2>(substr) >= end) {
1089 diag.emplace_back(DiagLevel::Critical, "Picture frame is incomplete (actual data is missing).", context);
1090 throw TruncatedDataException();
1091 }
1092 tagValue.assignData(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), TagDataType::Picture, dataEncoding);
1093}
1094
1101void Id3v2Frame::parseComment(const char *buffer, std::size_t dataSize, TagValue &tagValue, Diagnostics &diag)
1102{
1103 static const string context("parsing comment/unsynchronized lyrics frame");
1104 const char *end = buffer + dataSize;
1105 if (dataSize < 5) {
1106 diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete.", context);
1107 throw TruncatedDataException();
1108 }
1109 TagTextEncoding dataEncoding = parseTextEncodingByte(static_cast<std::uint8_t>(*buffer), diag);
1110 if (*(++buffer)) {
1111 tagValue.setLocale(Locale(std::string(buffer, 3), LocaleFormat::ISO_639_2_B)); // does standard say whether T or B?
1112 }
1113 auto substr = parseSubstring(buffer += 3, dataSize -= 4, dataEncoding, true, diag);
1114 tagValue.setDescription(string(get<0>(substr), get<1>(substr)), dataEncoding);
1115 if (get<2>(substr) > end) {
1116 diag.emplace_back(DiagLevel::Critical, "Comment frame is incomplete (description not terminated?).", context);
1117 throw TruncatedDataException();
1118 }
1119 substr = parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding, false, diag);
1120 tagValue.assignData(get<0>(substr), get<1>(substr), TagDataType::Text, dataEncoding);
1121}
1122
1127size_t Id3v2Frame::makeBom(char *buffer, TagTextEncoding encoding)
1128{
1129 switch (encoding) {
1131 LE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1132 return 2;
1134 BE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
1135 return 2;
1136 default:
1137 return 0;
1138 }
1139}
1140
1145 unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo, Diagnostics &diag)
1146{
1147 // determine description
1148 TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1149 StringData convertedDescription;
1150 string::size_type descriptionSize = picture.description().find(
1151 "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1152 if (descriptionSize == string::npos) {
1153 descriptionSize = picture.description().size();
1154 }
1155 if (descriptionEncoding == TagTextEncoding::Utf8) {
1156 // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1157 descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1158 convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1159 descriptionSize = convertedDescription.second;
1160 }
1161
1162 // calculate needed buffer size and create buffer
1163 // note: encoding byte + image format + picture type byte + description size + 1 or 2 null bytes (depends on encoding) + data size
1164 const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1165 + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1166 + picture.dataSize();
1167 if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
1168 diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making legacy picture frame");
1169 throw InvalidDataException();
1170 }
1171 buffer = make_unique<char[]>(bufferSize = static_cast<std::uint32_t>(requiredBufferSize));
1172 char *offset = buffer.get();
1173
1174 // write encoding byte
1175 *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1176
1177 // write mime type
1178 const char *imageFormat;
1179 if (picture.mimeType() == "image/jpeg") {
1180 imageFormat = "JPG";
1181 } else if (picture.mimeType() == "image/png") {
1182 imageFormat = "PNG";
1183 } else if (picture.mimeType() == "image/gif") {
1184 imageFormat = "GIF";
1185 } else if (picture.mimeType() == "-->") {
1186 imageFormat = picture.mimeType().data();
1187 } else {
1188 imageFormat = "UND";
1189 }
1190 std::memcpy(++offset, imageFormat, 3);
1191
1192 // write picture type
1193 *(offset += 3) = static_cast<char>(typeInfo);
1194
1195 // write description
1196 offset += makeBom(offset + 1, descriptionEncoding);
1197 if (convertedDescription.first) {
1198 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1199 } else {
1200 picture.description().copy(++offset, descriptionSize);
1201 }
1202 *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1203 if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1204 *(++offset) = 0x00;
1205 }
1206
1207 // write actual data
1208 copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1209}
1210
1214void Id3v2Frame::makePicture(std::unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &picture, std::uint8_t typeInfo,
1215 std::uint8_t version, Diagnostics &diag)
1216{
1217 if (version < 3) {
1218 makeLegacyPicture(buffer, bufferSize, picture, typeInfo, diag);
1219 return;
1220 }
1221
1222 // determine description
1223 TagTextEncoding descriptionEncoding = picture.descriptionEncoding();
1224 StringData convertedDescription;
1225 string::size_type descriptionSize = picture.description().find(
1226 "\0\0", 0, descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1227 if (descriptionSize == string::npos) {
1228 descriptionSize = picture.description().size();
1229 }
1230 if (version < 4 && descriptionEncoding == TagTextEncoding::Utf8) {
1231 // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1232 descriptionEncoding = TagTextEncoding::Utf16LittleEndian;
1233 convertedDescription = convertUtf8ToUtf16LE(picture.description().data(), descriptionSize);
1234 descriptionSize = convertedDescription.second;
1235 }
1236 // determine mime-type
1237 string::size_type mimeTypeSize = picture.mimeType().find('\0');
1238 if (mimeTypeSize == string::npos) {
1239 mimeTypeSize = picture.mimeType().length();
1240 }
1241
1242 // calculate needed buffer size and create buffer
1243 // note: encoding byte + mime type size + 0 byte + picture type byte + description size + 1 or 4 null bytes (depends on encoding) + data size
1244 const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1245 + (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian ? 4 : 1)
1246 + picture.dataSize();
1247 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1248 diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", "making picture frame");
1249 throw InvalidDataException();
1250 }
1251 buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1252 char *offset = buffer.get();
1253
1254 // write encoding byte
1255 *offset = static_cast<char>(makeTextEncodingByte(descriptionEncoding));
1256
1257 // write mime type
1258 picture.mimeType().copy(++offset, mimeTypeSize);
1259
1260 *(offset += mimeTypeSize) = 0x00; // terminate mime type
1261 // write picture type
1262 *(++offset) = static_cast<char>(typeInfo);
1263
1264 // write description
1265 offset += makeBom(offset + 1, descriptionEncoding);
1266 if (convertedDescription.first) {
1267 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1268 } else {
1269 picture.description().copy(++offset, descriptionSize);
1270 }
1271 *(offset += descriptionSize) = 0x00; // terminate description and increase data offset
1272 if (descriptionEncoding == TagTextEncoding::Utf16BigEndian || descriptionEncoding == TagTextEncoding::Utf16LittleEndian) {
1273 *(++offset) = 0x00;
1274 }
1275
1276 // write actual data
1277 copy(picture.dataPointer(), picture.dataPointer() + picture.dataSize(), ++offset);
1278}
1279
1283void Id3v2Frame::makeComment(unique_ptr<char[]> &buffer, std::uint32_t &bufferSize, const TagValue &comment, std::uint8_t version, Diagnostics &diag)
1284{
1285 static const string context("making comment frame");
1286
1287 // check whether type and other values are valid
1288 TagTextEncoding encoding = comment.dataEncoding();
1289 if (!comment.description().empty() && encoding != comment.descriptionEncoding()) {
1290 diag.emplace_back(DiagLevel::Critical, "Data encoding and description encoding aren't equal.", context);
1291 throw InvalidDataException();
1292 }
1293 const string &language = comment.locale().abbreviatedName(LocaleFormat::ISO_639_2_B, LocaleFormat::ISO_639_2_T, LocaleFormat::Unknown);
1294 if (language.length() > 3) {
1295 diag.emplace_back(DiagLevel::Critical, "The language must be 3 bytes long (ISO-639-2).", context);
1296 throw InvalidDataException();
1297 }
1298 StringData convertedDescription;
1299 string::size_type descriptionSize = comment.description().find(
1300 "\0\0", 0, encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 2 : 1);
1301 if (descriptionSize == string::npos) {
1302 descriptionSize = comment.description().size();
1303 }
1304 if (version < 4 && encoding == TagTextEncoding::Utf8) {
1305 // UTF-8 is only supported by ID3v2.4, so convert back to UTF-16
1307 convertedDescription = convertUtf8ToUtf16LE(comment.description().data(), descriptionSize);
1308 descriptionSize = convertedDescription.second;
1309 }
1310
1311 // calculate needed buffer size and create buffer
1312 // note: encoding byte + language + description size + actual data size + BOMs and termination
1313 const auto data = comment.toString(encoding);
1314 const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
1315 + (encoding == TagTextEncoding::Utf16BigEndian || encoding == TagTextEncoding::Utf16LittleEndian ? 6 : 1) + data.size();
1316 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1317 diag.emplace_back(DiagLevel::Critical, "Required size exceeds maximum.", context);
1318 throw InvalidDataException();
1319 }
1320 buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1321 char *offset = buffer.get();
1322
1323 // write encoding
1324 *offset = static_cast<char>(makeTextEncodingByte(encoding));
1325
1326 // write language
1327 for (unsigned int i = 0; i < 3; ++i) {
1328 *(++offset) = (language.length() > i) ? language[i] : 0x00;
1329 }
1330
1331 // write description
1332 offset += makeBom(offset + 1, encoding);
1333 if (convertedDescription.first) {
1334 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1335 } else {
1336 comment.description().copy(++offset, descriptionSize);
1337 }
1338 offset += descriptionSize;
1339 *offset = 0x00; // terminate description and increase data offset
1341 *(++offset) = 0x00;
1342 }
1343
1344 // write actual data
1345 offset += makeBom(offset + 1, encoding);
1346 data.copy(++offset, data.size());
1347}
1348
1349} // 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 isUrlFrame(std::uint32_t id)
Returns an indication whether the specified id is a URL 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.