Tag Parser 12.1.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
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....
Definition exceptions.h:18
The PositionInSet class describes the position of an element in a set which consists of a certain num...
constexpr std::int32_t position() const
Returns the element position of the current instance.
The TagValue class wraps values of different types.
Definition tagvalue.h:147
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:203
std::uint64_t m_size
Definition tag.h:204
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
KnownField
Specifies the field.
Definition tag.h:29
TagTextEncoding
Specifies the text encoding.
Definition tagvalue.h:29
TagType
Specifies the tag type.
Definition tagtype.h:11