Tag Parser: id3/id3v1tag.cpp Source File

Tag Parser 12.5.2 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
21
25Id3v1Tag::Id3v1Tag()
26{
27}
28
29 TagType Id3v1Tag::type() const
30{
31 return TagType::Id3v1Tag;
32}
33
34std::string_view Id3v1Tag::typeName() const
35{
36 return tagName;
37}
38
47 bool Id3v1Tag::canEncodingBeUsed(TagTextEncoding encoding) const
48{
49 return encoding == TagTextEncoding::Latin1;
50}
51
58 void 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) {
64 throw NoDataFoundException();
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
93 void 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
144 const TagValue &Id3v1Tag::value(KnownField field) const
145{
146 switch (field) {
147 case KnownField::Title:
148 return m_title;
149 case KnownField::Artist:
150 return m_artist;
151 case KnownField::Album:
152 return m_album;
153 case KnownField::RecordDate:
154 return m_year;
155 case KnownField::Comment:
156 return m_comment;
157 case KnownField::TrackPosition:
158 return m_trackPos;
159 case KnownField::Genre:
160 return m_genre;
161 default:
162 return TagValue::empty();
163 }
164}
165
166 bool Id3v1Tag::setValue(KnownField field, const TagValue &value)
167{
168 switch (field) {
169 case KnownField::Title:
170 m_title = value;
171 break;
172 case KnownField::Artist:
173 m_artist = value;
174 break;
175 case KnownField::Album:
176 m_album = value;
177 break;
178 case KnownField::RecordDate:
179 m_year = value;
180 break;
181 case KnownField::Comment:
182 m_comment = value;
183 break;
184 case KnownField::TrackPosition:
185 m_trackPos = value;
186 break;
187 case KnownField::Genre:
188 m_genre = value;
189 break;
190 default:
191 return false;
192 }
193 return true;
194}
195
196 bool Id3v1Tag::setValueConsideringTypeInfo(KnownField field, const TagValue &value, const string &)
197{
198 return setValue(field, value);
199}
200
201 bool Id3v1Tag::hasField(KnownField field) const
202{
203 switch (field) {
204 case KnownField::Title:
205 return !m_title.isEmpty();
206 case KnownField::Artist:
207 return !m_artist.isEmpty();
208 case KnownField::Album:
209 return !m_album.isEmpty();
210 return !m_year.isEmpty();
211 case KnownField::Comment:
212 return !m_comment.isEmpty();
213 case KnownField::TrackPosition:
214 return !m_trackPos.isEmpty();
215 case KnownField::Genre:
216 return !m_genre.isEmpty();
217 default:
218 return false;
219 }
220}
221
222 void Id3v1Tag::removeAllFields()
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
244 bool Id3v1Tag::supportsField(KnownField field) const
245{
246 switch (field) {
247 case KnownField::Title:
248 case KnownField::Artist:
249 case KnownField::Album:
250 case KnownField::RecordDate:
251 case KnownField::Comment:
252 case KnownField::TrackPosition:
253 case KnownField::Genre:
254 return true;
255 default:
256 return false;
257 }
258}
259
260 void Id3v1Tag::ensureTextValuesAreProperlyEncoded()
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()) {
265 case TagTextEncoding::Latin1:
266 case TagTextEncoding::Utf8:
267 case TagTextEncoding::Unspecified:
268 break;
269 default:
270 value->convertDataEncoding(TagTextEncoding::Utf8);
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 {
291 value.assignData(buffer, maxLength, TagDataType::Text, TagTextEncoding::Latin1);
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()) {
317 case TagTextEncoding::Latin1:
318 break;
319 case TagTextEncoding::Utf8:
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
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition diagnostics.h:156
TagParser::Id3v1Tag::Id3v1Tag
Id3v1Tag()
Constructs a new tag.
Definition id3v1tag.cpp:25
TagParser::Id3v1Tag::make
void make(std::ostream &targetStream, Diagnostics &diag)
Writes tag information to the specified stream.
Definition id3v1tag.cpp:93
TagParser::Id3v1Tag::ensureTextValuesAreProperlyEncoded
void ensureTextValuesAreProperlyEncoded() override
Ensures the encoding of all assigned text values is supported by the tag by converting the character ...
Definition id3v1tag.cpp:260
TagParser::Id3v1Tag::fieldCount
std::size_t fieldCount() const override
Returns the number of present fields.
Definition id3v1tag.cpp:233
TagParser::Id3v1Tag::canEncodingBeUsed
bool canEncodingBeUsed(TagTextEncoding encoding) const override
Returns only true for TagTextEncoding::Latin1.
Definition id3v1tag.cpp:47
TagParser::Id3v1Tag::hasField
bool hasField(KnownField field) const override
Returns an indication whether the specified field is present.
Definition id3v1tag.cpp:201
TagParser::Id3v1Tag::value
const TagValue & value(KnownField value) const override
Returns the value of the specified field.
Definition id3v1tag.cpp:144
TagParser::Id3v1Tag::supportsField
bool supportsField(KnownField field) const override
Returns an indication whether the specified field is supported by the tag.
Definition id3v1tag.cpp:244
TagParser::Id3v1Tag::removeAllFields
void removeAllFields() override
Removes all fields from the tag.
Definition id3v1tag.cpp:222
TagParser::Id3v1Tag::setValueConsideringTypeInfo
bool setValueConsideringTypeInfo(KnownField field, const TagValue &value, const std::string &typeInfo)
Definition id3v1tag.cpp:196
TagParser::Id3v1Tag::tagName
static constexpr std::string_view tagName
Definition id3v1tag.h:15
TagParser::Id3v1Tag::type
TagType type() const override
Returns the type of the tag as TagParser::TagType.
Definition id3v1tag.cpp:29
TagParser::Id3v1Tag::typeName
std::string_view typeName() const override
Returns the type name of the tag as C-style string.
Definition id3v1tag.cpp:34
TagParser::Id3v1Tag::setValue
bool setValue(KnownField field, const TagValue &value) override
Assigns the given value to the specified field.
Definition id3v1tag.cpp:166
TagParser::Id3v1Tag::parse
void parse(std::istream &sourceStream, Diagnostics &diag)
Parses tag information from the specified stream.
Definition id3v1tag.cpp:58
TagParser::NoDataFoundException
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition exceptions.h:18
TagParser::TagValue
The TagValue class wraps values of different types.
Definition tagvalue.h:147
TagParser::TagValue::empty
static const TagValue & empty()
Returns a default-constructed TagValue where TagValue::isNull() and TagValue::isEmpty() both return t...
Definition tagvalue.cpp:1432
TagParser::Tag::m_version
std::string m_version
Definition tag.h:216
TagParser::Tag::m_size
std::uint64_t m_size
Definition tag.h:217
diagnostics.h
exceptions.h
id3v1tag.h
CppUtilities
Definition abstractcontainer.h:16
TagParser
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
TagParser::KnownField
KnownField
Specifies the field.
Definition tag.h:37
TagParser::KnownField::Comment
@ Comment
Definition tag.h:43
TagParser::KnownField::TrackPosition
@ TrackPosition
Definition tag.h:47
TagParser::KnownField::Artist
@ Artist
Definition tag.h:41
TagParser::KnownField::Genre
@ Genre
Definition tag.h:42
TagParser::KnownField::Title
@ Title
Definition tag.h:39
TagParser::KnownField::Album
@ Album
Definition tag.h:40
TagParser::KnownField::RecordDate
@ RecordDate
Definition tag.h:52
TagParser::TagTextEncoding
TagTextEncoding
Specifies the text encoding.
Definition tagvalue.h:29
TagParser::TagTextEncoding::Latin1
@ Latin1
Definition tagvalue.h:30
TagParser::TagTextEncoding::Unspecified
@ Unspecified
Definition tagvalue.h:34
TagParser::TagTextEncoding::Utf8
@ Utf8
Definition tagvalue.h:31
TagParser::TagType
TagType
Specifies the tag type.
Definition tagtype.h:11
TagParser::TagType::Id3v1Tag
@ Id3v1Tag
Definition tagtype.h:13
TagParser::DiagLevel::Warning
@ Warning
Definition diagnostics.h:20
TagParser::TagDataType::PositionInSet
@ PositionInSet
Definition tagvalue.h:122
TagParser::TagDataType::Text
@ Text
Definition tagvalue.h:120