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