Tag Parser 11.2.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
id3v1tag.cpp
Go to the documentation of this file.
1#include "./id3v1tag.h"
2#include "./id3genres.h"
3
4#include "../diagnostics.h"
5#include "../exceptions.h"
6
7#include <c++utilities/conversion/conversionexception.h>
8#include <c++utilities/conversion/stringbuilder.h>
9
10#include <cstring>
11#include <initializer_list>
12
13using namespace std;
14using namespace CppUtilities;
15
16namespace TagParser {
17
27{
28}
29
31{
32 return TagType::Id3v1Tag;
33}
34
35std::string_view Id3v1Tag::typeName() const
36{
37 return tagName;
38}
39
49{
50 return encoding == TagTextEncoding::Latin1;
51}
52
59void Id3v1Tag::parse(std::istream &stream, Diagnostics &diag)
60{
61 CPP_UTILITIES_UNUSED(diag)
62 char buffer[128];
63 stream.read(buffer, 128);
64 if (buffer[0] != 0x54 || buffer[1] != 0x41 || buffer[2] != 0x47) {
66 }
67 m_size = 128;
68 readValue(m_title, 30, buffer + 3);
69 readValue(m_artist, 30, buffer + 33);
70 readValue(m_album, 30, buffer + 63);
71 readValue(m_year, 4, buffer + 93);
72 const auto is11 = buffer[125] == 0;
73 if (is11) {
74 readValue(m_comment, 28, buffer + 97);
75 m_version = "1.1";
76 } else {
77 readValue(m_comment, 30, buffer + 97);
78 m_version = "1.0";
79 }
80 readValue(m_comment, is11 ? 28 : 30, buffer + 97);
81 if (is11) {
82 m_trackPos.assignPosition(PositionInSet(*reinterpret_cast<char *>(buffer + 126), 0));
83 }
84 m_genre.assignStandardGenreIndex(*reinterpret_cast<unsigned char *>(buffer + 127));
85}
86
94void Id3v1Tag::make(ostream &stream, Diagnostics &diag)
95{
96 static const string context("making ID3v1 tag");
97 char buffer[30];
98 buffer[0] = 0x54;
99 buffer[1] = 0x41;
100 buffer[2] = 0x47;
101 stream.write(buffer, 3);
102
103 // write text fields
104 writeValue(m_title, 30, buffer, stream, diag);
105 writeValue(m_artist, 30, buffer, stream, diag);
106 writeValue(m_album, 30, buffer, stream, diag);
107 writeValue(m_year, 4, buffer, stream, diag);
108 writeValue(m_comment, 28, buffer, stream, diag);
109
110 // set "default" values for numeric fields
111 buffer[0] = 0x0; // empty byte
112 buffer[1] = 0x0; // track number
113 buffer[2] = 0x0; // genre
114
115 // write track
116 if (!m_trackPos.isEmpty()) {
117 try {
118 const auto position(m_trackPos.toPositionInSet().position());
119 if (position < 0x00 || position > 0xFF) {
120 throw ConversionException();
121 }
122 buffer[1] = static_cast<char>(position);
123 } catch (const ConversionException &) {
124 diag.emplace_back(
125 DiagLevel::Warning, "Track position field can not be set because given value can not be converted appropriately.", context);
126 }
127 }
128
129 // write genre
130 try {
131 const auto genreIndex(m_genre.toStandardGenreIndex());
132 if (genreIndex < 0x00 || genreIndex > 0xFF) {
133 throw ConversionException();
134 }
135 buffer[2] = static_cast<char>(genreIndex);
136 } catch (const ConversionException &) {
137 diag.emplace_back(DiagLevel::Warning,
138 "Genre field can not be set because given value can not be converted to a standard genre number supported by ID3v1.", context);
139 }
140
141 stream.write(buffer, 3);
142 stream.flush();
143}
144
146{
147 switch (field) {
149 return m_title;
151 return m_artist;
153 return m_album;
155 return m_year;
157 return m_comment;
159 return m_trackPos;
161 return m_genre;
162 default:
163 return TagValue::empty();
164 }
165}
166
167bool Id3v1Tag::setValue(KnownField field, const TagValue &value)
168{
169 switch (field) {
171 m_title = value;
172 break;
174 m_artist = value;
175 break;
177 m_album = value;
178 break;
180 m_year = value;
181 break;
183 m_comment = value;
184 break;
186 m_trackPos = value;
187 break;
189 m_genre = value;
190 break;
191 default:
192 return false;
193 }
194 return true;
195}
196
197bool Id3v1Tag::setValueConsideringTypeInfo(KnownField field, const TagValue &value, const string &)
198{
199 return setValue(field, value);
200}
201
203{
204 switch (field) {
206 return !m_title.isEmpty();
208 return !m_artist.isEmpty();
210 return !m_album.isEmpty();
211 return !m_year.isEmpty();
213 return !m_comment.isEmpty();
215 return !m_trackPos.isEmpty();
217 return !m_genre.isEmpty();
218 default:
219 return false;
220 }
221}
222
224{
225 m_title.clearDataAndMetadata();
226 m_artist.clearDataAndMetadata();
227 m_album.clearDataAndMetadata();
228 m_year.clearDataAndMetadata();
229 m_comment.clearDataAndMetadata();
230 m_trackPos.clearDataAndMetadata();
231 m_genre.clearDataAndMetadata();
232}
233
234std::size_t Id3v1Tag::fieldCount() const
235{
236 auto count = std::size_t(0);
237 for (const auto &value : std::initializer_list<const TagValue *>{ &m_title, &m_artist, &m_album, &m_year, &m_comment, &m_trackPos, &m_genre }) {
238 if (!value->isEmpty()) {
239 ++count;
240 }
241 }
242 return count;
243}
244
246{
247 switch (field) {
255 return true;
256 default:
257 return false;
258 }
259}
260
262{
263 for (auto *value : initializer_list<TagValue *>{ &m_title, &m_artist, &m_album, &m_year, &m_comment, &m_trackPos, &m_genre }) {
264 // convert UTF-16 to UTF-8
265 switch (value->dataEncoding()) {
269 break;
270 default:
272 }
273 }
274}
275
279void Id3v1Tag::readValue(TagValue &value, size_t maxLength, const char *buffer)
280{
281 const char *end = buffer + maxLength - 1;
282 while ((*end == 0x0 || *end == ' ') && end >= buffer) {
283 --end;
284 --maxLength;
285 }
286 if (buffer == end) {
287 return;
288 }
289 if (maxLength >= 3 && BE::toUInt24(buffer) == 0x00EFBBBF) {
290 value.assignData(buffer + 3, maxLength - 3, TagDataType::Text, TagTextEncoding::Utf8);
291 } else {
293 }
294}
295
299void Id3v1Tag::writeValue(const TagValue &value, size_t length, char *buffer, ostream &targetStream, Diagnostics &diag)
300{
301 // initialize buffer with zeroes
302 memset(buffer, 0, length);
303
304 // stringify value
305 string valueAsString;
306 try {
307 valueAsString = value.toString();
308 } catch (const ConversionException &) {
309 diag.emplace_back(
310 DiagLevel::Warning, "Field can not be set because given value can not be converted appropriately.", "making ID3v1 tag field");
311 }
312
313 // handle encoding
314 auto *valueStart = buffer;
315 auto valueLength = length;
316 auto hasProblematicEncoding = false;
317 switch (value.dataEncoding()) {
319 break;
321 // write UTF-8 BOM if the value contains non-ASCII characters
322 for (const auto c : valueAsString) {
323 if ((c & 0x80) == 0) {
324 continue;
325 }
326 buffer[0] = static_cast<char>(0xEF);
327 buffer[1] = static_cast<char>(0xBB);
328 buffer[2] = static_cast<char>(0xBF);
329 valueStart += 3;
330 valueLength -= 3;
331 hasProblematicEncoding = true;
332 break;
333 }
334 break;
335 default:
336 hasProblematicEncoding = true;
337 }
338 if (hasProblematicEncoding) {
339 diag.emplace_back(DiagLevel::Warning, "The used encoding is unlikely to be supported by other software.", "making ID3v1 tag field");
340 }
341
342 // copy the string
343 if (valueAsString.size() > length) {
344 diag.emplace_back(
345 DiagLevel::Warning, argsToString("Value has been truncated. Max. ", length, " characters supported."), "making ID3v1 tag field");
346 }
347 valueAsString.copy(valueStart, valueLength);
348
349 targetStream.write(buffer, static_cast<streamsize>(length));
350}
351
352} // namespace TagParser
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
Id3v1Tag()
Constructs a new tag.
Definition: id3v1tag.cpp:26
void make(std::ostream &targetStream, Diagnostics &diag)
Writes tag information to the specified stream.
Definition: id3v1tag.cpp:94
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
Definition: id3v1tag.cpp:261
std::size_t fieldCount() const override
Returns the number of present fields.
Definition: id3v1tag.cpp:234
bool canEncodingBeUsed(TagTextEncoding encoding) const override
Returns only true for TagTextEncoding::Latin1.
Definition: id3v1tag.cpp:48
bool hasField(KnownField field) const override
Returns an indication whether the specified field is present.
Definition: id3v1tag.cpp:202
const TagValue & value(KnownField value) const override
Returns the value of the specified field.
Definition: id3v1tag.cpp:145
bool supportsField(KnownField field) const override
Returns an indication whether the specified field is supported by the tag.
Definition: id3v1tag.cpp:245
void removeAllFields() override
Removes all fields from the tag.
Definition: id3v1tag.cpp:223
bool setValueConsideringTypeInfo(KnownField field, const TagValue &value, const std::string &typeInfo)
Definition: id3v1tag.cpp:197
static constexpr std::string_view tagName
Definition: id3v1tag.h:15
TagType type() const override
Returns the type of the tag as TagParser::TagType.
Definition: id3v1tag.cpp:30
std::string_view typeName() const override
Returns the type name of the tag as C-style string.
Definition: id3v1tag.cpp:35
bool setValue(KnownField field, const TagValue &value) override
Assigns the given value to the specified field.
Definition: id3v1tag.cpp:167
void parse(std::istream &sourceStream, Diagnostics &diag)
Parses tag information from the specified stream.
Definition: id3v1tag.cpp:59
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:21
constexpr std::int32_t position() const
Returns the element position of the current instance.
Definition: positioninset.h:78
The TagValue class wraps values of different types.
Definition: tagvalue.h:95
TagTextEncoding dataEncoding() const
Returns the data encoding.
Definition: tagvalue.h:753
void assignPosition(PositionInSet value)
Assigns the given PositionInSet value.
Definition: tagvalue.h:428
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
PositionInSet toPositionInSet() const
Converts the value of the current TagValue object to its equivalent PositionInSet representation.
Definition: tagvalue.cpp:401
void clearDataAndMetadata()
Wipes assigned data including meta data.
Definition: tagvalue.h:547
void convertDataEncoding(TagTextEncoding encoding)
Converts the currently assigned text value to the specified encoding.
Definition: tagvalue.cpp:504
void assignStandardGenreIndex(int index)
Assigns the given standard genre index to be assigned.
Definition: tagvalue.h:459
std::string toString(TagTextEncoding encoding=TagTextEncoding::Unspecified) const
Converts the value of the current TagValue object to its equivalent std::string representation.
Definition: tagvalue.h:485
static const TagValue & empty()
Returns a default-constructed TagValue where TagValue::isNull() and TagValue::isEmpty() both return t...
Definition: tagvalue.cpp:958
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:525
int toStandardGenreIndex() const
Converts the value of the current TagValue object to its equivalent standard genre index.
Definition: tagvalue.cpp:360
std::string m_version
Definition: tag.h:214
std::uint64_t m_size
Definition: tag.h:215
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
KnownField
Specifies the field.
Definition: tag.h:42
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:28
TagType
Specifies the tag type.
Definition: tag.h:20