Tag Parser  9.4.0
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 
13 using namespace std;
14 using namespace CppUtilities;
15 
16 namespace TagParser {
17 
26 Id3v1Tag::Id3v1Tag()
27 {
28 }
29 
30 TagType Id3v1Tag::type() const
31 {
32  return TagType::Id3v1Tag;
33 }
34 
35 const char *Id3v1Tag::typeName() const
36 {
37  return tagName;
38 }
39 
48 bool Id3v1Tag::canEncodingBeUsed(TagTextEncoding encoding) const
49 {
50  return encoding == TagTextEncoding::Latin1;
51 }
52 
59 void 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) {
65  throw NoDataFoundException();
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 
94 void 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 
145 const TagValue &Id3v1Tag::value(KnownField field) const
146 {
147  switch (field) {
148  case KnownField::Title:
149  return m_title;
150  case KnownField::Artist:
151  return m_artist;
152  case KnownField::Album:
153  return m_album;
154  case KnownField::RecordDate:
155  case KnownField::Year:
156  return m_year;
157  case KnownField::Comment:
158  return m_comment;
160  return m_trackPos;
161  case KnownField::Genre:
162  return m_genre;
163  default:
164  return TagValue::empty();
165  }
166 }
167 
168 bool Id3v1Tag::setValue(KnownField field, const TagValue &value)
169 {
170  switch (field) {
171  case KnownField::Title:
172  m_title = value;
173  break;
174  case KnownField::Artist:
175  m_artist = value;
176  break;
177  case KnownField::Album:
178  m_album = value;
179  break;
180  case KnownField::RecordDate:
181  case KnownField::Year:
182  m_year = value;
183  break;
184  case KnownField::Comment:
185  m_comment = value;
186  break;
188  m_trackPos = value;
189  break;
190  case KnownField::Genre:
191  m_genre = value;
192  break;
193  default:
194  return false;
195  }
196  return true;
197 }
198 
199 bool Id3v1Tag::setValueConsideringTypeInfo(KnownField field, const TagValue &value, const string &)
200 {
201  return setValue(field, value);
202 }
203 
204 bool Id3v1Tag::hasField(KnownField field) const
205 {
206  switch (field) {
207  case KnownField::Title:
208  return !m_title.isEmpty();
209  case KnownField::Artist:
210  return !m_artist.isEmpty();
211  case KnownField::Album:
212  return !m_album.isEmpty();
213  case KnownField::Year:
214  return !m_year.isEmpty();
215  case KnownField::Comment:
216  return !m_comment.isEmpty();
218  return !m_trackPos.isEmpty();
219  case KnownField::Genre:
220  return !m_genre.isEmpty();
221  default:
222  return false;
223  }
224 }
225 
226 void Id3v1Tag::removeAllFields()
227 {
228  m_title.clearDataAndMetadata();
229  m_artist.clearDataAndMetadata();
230  m_album.clearDataAndMetadata();
231  m_year.clearDataAndMetadata();
232  m_comment.clearDataAndMetadata();
233  m_trackPos.clearDataAndMetadata();
234  m_genre.clearDataAndMetadata();
235 }
236 
237 unsigned int Id3v1Tag::fieldCount() const
238 {
239  unsigned int count = 0;
240  for (const auto &value : { m_title, m_artist, m_album, m_year, m_comment, m_trackPos, m_genre }) {
241  if (!value.isEmpty()) {
242  ++count;
243  }
244  }
245  return count;
246 }
247 
248 bool Id3v1Tag::supportsField(KnownField field) const
249 {
250  switch (field) {
251  case KnownField::Title:
252  case KnownField::Artist:
253  case KnownField::Album:
254  case KnownField::RecordDate:
255  case KnownField::Year:
256  case KnownField::Comment:
258  case KnownField::Genre:
259  return true;
260  default:
261  return false;
262  }
263 }
264 
265 void Id3v1Tag::ensureTextValuesAreProperlyEncoded()
266 {
267  for (auto *value : initializer_list<TagValue *>{ &m_title, &m_artist, &m_album, &m_year, &m_comment, &m_trackPos, &m_genre }) {
268  // convert UTF-16 to UTF-8
269  switch (value->dataEncoding()) {
270  case TagTextEncoding::Latin1:
272  case TagTextEncoding::Unspecified:
273  break;
274  default:
275  value->convertDataEncoding(TagTextEncoding::Utf8);
276  }
277  }
278 }
279 
283 void Id3v1Tag::readValue(TagValue &value, size_t maxLength, const char *buffer)
284 {
285  const char *end = buffer + maxLength - 1;
286  while ((*end == 0x0 || *end == ' ') && end > buffer) {
287  --end;
288  --maxLength;
289  }
290  if (maxLength >= 3 && BE::toUInt24(buffer) == 0x00EFBBBF) {
291  value.assignData(buffer + 3, maxLength - 3, TagDataType::Text, TagTextEncoding::Utf8);
292  } else {
293  value.assignData(buffer, maxLength, TagDataType::Text, TagTextEncoding::Latin1);
294  }
295 }
296 
300 void Id3v1Tag::writeValue(const TagValue &value, size_t length, char *buffer, ostream &targetStream, Diagnostics &diag)
301 {
302  // initialize buffer with zeroes
303  memset(buffer, 0, length);
304 
305  // stringify value
306  string valueAsString;
307  try {
308  valueAsString = value.toString();
309  } catch (const ConversionException &) {
310  diag.emplace_back(
311  DiagLevel::Warning, "Field can not be set because given value can not be converted appropriately.", "making ID3v1 tag field");
312  }
313 
314  // handle encoding
315  auto *valueStart = buffer;
316  auto valueLength = length;
317  switch (value.dataEncoding()) {
318  case TagTextEncoding::Latin1:
319  case TagTextEncoding::Unspecified:
320  break;
322  // write
323  for (const auto c : valueAsString) {
324  if ((c & 0x80) == 0) {
325  continue;
326  }
327  buffer[0] = static_cast<char>(0xEF);
328  buffer[1] = static_cast<char>(0xBB);
329  buffer[2] = static_cast<char>(0xBF);
330  valueStart += 3;
331  valueLength -= 3;
332  break;
333  }
334  [[fallthrough]];
335  default:
336  diag.emplace_back(DiagLevel::Warning, "The used encoding is unlikely to be supported by other software.", "making ID3v1 tag field");
337  }
338 
339  // copy the string
340  if (valueAsString.size() > length) {
341  diag.emplace_back(
342  DiagLevel::Warning, argsToString("Value has been truncated. Max. ", length, " characters supported."), "making ID3v1 tag field");
343  }
344  valueAsString.copy(valueStart, valueLength);
345 
346  targetStream.write(buffer, static_cast<streamsize>(length));
347 }
348 
349 } // namespace TagParser
TagParser::Mp4TagAtomIds::Album
@ Album
Definition: mp4ids.h:86
TagParser::RawDataType::Utf8
@ Utf8
Definition: mp4tagfield.h:21
TagParser::TagTextEncoding
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:25
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
TagParser::Mp4TagAtomIds::TrackPosition
@ TrackPosition
Definition: mp4ids.h:116
TagParser
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
TagParser::MatroskaIds::Title
@ Title
Definition: matroskaid.h:54
id3v1tag.h
TagParser::Mp4TagAtomIds::Year
@ Year
Definition: mp4ids.h:122
id3genres.h
CppUtilities
Definition: abstractcontainer.h:15
TagParser::Mp4TagAtomIds::Genre
@ Genre
Definition: mp4ids.h:101
TagParser::KnownField
KnownField
Specifies the field.
Definition: tag.h:42
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:75
TagParser::PositionInSet
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:21
TagParser::Mp4TagAtomIds::Artist
@ Artist
Definition: mp4ids.h:88
TagParser::TagValue::assignData
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
TagParser::Mp4TagAtomIds::Comment
@ Comment
Definition: mp4ids.h:91
TagParser::TagType
TagType
Specifies the tag type.
Definition: tag.h:20