Tag Parser  7.1.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 
9 #include <cstring>
10 
11 using namespace std;
12 using namespace ConversionUtilities;
13 
14 namespace TagParser {
15 
24 Id3v1Tag::Id3v1Tag()
25 {
26 }
27 
28 TagType Id3v1Tag::type() const
29 {
30  return TagType::Id3v1Tag;
31 }
32 
33 const char *Id3v1Tag::typeName() const
34 {
35  return tagName;
36 }
37 
38 bool Id3v1Tag::canEncodingBeUsed(TagTextEncoding encoding) const
39 {
40  return Tag::canEncodingBeUsed(encoding);
41 }
42 
51 void Id3v1Tag::parse(std::istream &stream, Diagnostics &diag)
52 {
53  VAR_UNUSED(diag);
54  char buffer[128];
55  stream.read(buffer, 128);
56  if (buffer[0] != 0x54 || buffer[1] != 0x41 || buffer[2] != 0x47) {
57  throw NoDataFoundException();
58  }
59  m_size = 128;
60  readValue(m_title, 30, buffer + 3);
61  readValue(m_artist, 30, buffer + 33);
62  readValue(m_album, 30, buffer + 63);
63  readValue(m_year, 4, buffer + 93);
64  if (buffer[125] == 0) {
65  readValue(m_comment, 28, buffer + 97);
66  m_version = "1.1";
67  } else {
68  readValue(m_comment, 30, buffer + 97);
69  m_version = "1.0";
70  }
71  readValue(m_comment, buffer[125] == 0 ? 28 : 30, buffer + 97);
72  if (buffer[125] == 0) {
73  m_trackPos.assignPosition(PositionInSet(*reinterpret_cast<char *>(buffer + 126), 0));
74  }
75  m_genre.assignStandardGenreIndex(*reinterpret_cast<unsigned char *>(buffer + 127));
76 }
77 
85 void Id3v1Tag::make(ostream &stream, Diagnostics &diag)
86 {
87  static const string context("making ID3v1 tag");
88  char buffer[30];
89  buffer[0] = 0x54;
90  buffer[1] = 0x41;
91  buffer[2] = 0x47;
92  stream.write(buffer, 3);
93 
94  // write text fields
95  writeValue(m_title, 30, buffer, stream, diag);
96  writeValue(m_artist, 30, buffer, stream, diag);
97  writeValue(m_album, 30, buffer, stream, diag);
98  writeValue(m_year, 4, buffer, stream, diag);
99  writeValue(m_comment, 28, buffer, stream, diag);
100 
101  // set "default" values for numeric fields
102  buffer[0] = 0x0; // empty byte
103  buffer[1] = 0x0; // track number
104  buffer[2] = 0x0; // genre
105 
106  // write track
107  if (!m_trackPos.isEmpty()) {
108  try {
109  const auto position(m_trackPos.toPositionInSet().position());
110  if (position < 0x00 || position > 0xFF) {
111  throw ConversionException();
112  }
113  buffer[1] = static_cast<char>(position);
114  } catch (const ConversionException &) {
115  diag.emplace_back(
116  DiagLevel::Warning, "Track position field can not be set because given value can not be converted appropriately.", context);
117  }
118  }
119 
120  // write genre
121  try {
122  const auto genreIndex(m_genre.toStandardGenreIndex());
123  if (genreIndex < 0x00 || genreIndex > 0xFF) {
124  throw ConversionException();
125  }
126  buffer[2] = static_cast<char>(genreIndex);
127  } catch (const ConversionException &) {
128  diag.emplace_back(DiagLevel::Warning,
129  "Genre field can not be set because given value can not be converted to a standard genre number supported by ID3v1.", context);
130  }
131 
132  stream.write(buffer, 3);
133  stream.flush();
134 }
135 
136 const TagValue &Id3v1Tag::value(KnownField field) const
137 {
138  switch (field) {
139  case KnownField::Title:
140  return m_title;
141  case KnownField::Artist:
142  return m_artist;
143  case KnownField::Album:
144  return m_album;
145  case KnownField::Year:
146  return m_year;
147  case KnownField::Comment:
148  return m_comment;
150  return m_trackPos;
151  case KnownField::Genre:
152  return m_genre;
153  default:
154  return TagValue::empty();
155  }
156 }
157 
158 bool Id3v1Tag::setValue(KnownField field, const TagValue &value)
159 {
160  switch (field) {
161  case KnownField::Title:
162  m_title = value;
163  break;
164  case KnownField::Artist:
165  m_artist = value;
166  break;
167  case KnownField::Album:
168  m_album = value;
169  break;
170  case KnownField::Year:
171  m_year = value;
172  break;
173  case KnownField::Comment:
174  m_comment = value;
175  break;
177  m_trackPos = value;
178  break;
179  case KnownField::Genre:
180  m_genre = value;
181  break;
182  default:
183  return false;
184  }
185  return true;
186 }
187 
188 bool Id3v1Tag::setValueConsideringTypeInfo(KnownField field, const TagValue &value, const string &)
189 {
190  return setValue(field, value);
191 }
192 
193 bool Id3v1Tag::hasField(KnownField field) const
194 {
195  switch (field) {
196  case KnownField::Title:
197  return !m_title.isEmpty();
198  case KnownField::Artist:
199  return !m_artist.isEmpty();
200  case KnownField::Album:
201  return !m_album.isEmpty();
202  case KnownField::Year:
203  return !m_year.isEmpty();
204  case KnownField::Comment:
205  return !m_comment.isEmpty();
207  return !m_trackPos.isEmpty();
208  case KnownField::Genre:
209  return !m_genre.isEmpty();
210  default:
211  return false;
212  }
213 }
214 
215 void Id3v1Tag::removeAllFields()
216 {
217  m_title.clearDataAndMetadata();
218  m_artist.clearDataAndMetadata();
219  m_album.clearDataAndMetadata();
220  m_year.clearDataAndMetadata();
221  m_comment.clearDataAndMetadata();
222  m_trackPos.clearDataAndMetadata();
223  m_genre.clearDataAndMetadata();
224 }
225 
226 unsigned int Id3v1Tag::fieldCount() const
227 {
228  unsigned int count = 0;
229  for (const auto &value : { m_title, m_artist, m_album, m_year, m_comment, m_trackPos, m_genre }) {
230  if (!value.isEmpty()) {
231  ++count;
232  }
233  }
234  return count;
235 }
236 
237 bool Id3v1Tag::supportsField(KnownField field) const
238 {
239  switch (field) {
240  case KnownField::Title:
241  case KnownField::Artist:
242  case KnownField::Album:
243  case KnownField::Year:
244  case KnownField::Comment:
246  case KnownField::Genre:
247  return true;
248  default:
249  return false;
250  }
251 }
252 
253 void Id3v1Tag::ensureTextValuesAreProperlyEncoded()
254 {
255  m_title.convertDataEncodingForTag(this);
256  m_artist.convertDataEncodingForTag(this);
257  m_album.convertDataEncodingForTag(this);
258  m_year.convertDataEncodingForTag(this);
259  m_comment.convertDataEncodingForTag(this);
260  m_trackPos.convertDataEncodingForTag(this);
261  m_genre.convertDataEncodingForTag(this);
262 }
263 
267 void Id3v1Tag::readValue(TagValue &value, size_t maxLength, const char *buffer)
268 {
269  const char *end = buffer + maxLength - 1;
270  while ((*end == 0x0 || *end == ' ') && end >= buffer) {
271  --end;
272  --maxLength;
273  }
274  value.assignData(buffer, maxLength, TagDataType::Text, TagTextEncoding::Latin1);
275 }
276 
280 void Id3v1Tag::writeValue(const TagValue &value, size_t length, char *buffer, ostream &targetStream, Diagnostics &diag)
281 {
282  memset(buffer, 0, length);
283  try {
284  value.toString().copy(buffer, length);
285  } catch (const ConversionException &) {
286  diag.emplace_back(
287  DiagLevel::Warning, "Field can not be set because given value can not be converted appropriately.", "making ID3v1 tag field");
288  }
289  targetStream.write(buffer, length);
290 }
291 
292 } // namespace TagParser
STL namespace.
The PositionInSet class describes the position of an element in a set which consists of a certain num...
Definition: positioninset.h:21
KnownField
Specifies the field.
Definition: tag.h:40
The exception that is thrown when the data to be parsed holds no parsable information.
Definition: exceptions.h:18
void assignData(const char *data, std::size_t length, TagDataType type=TagDataType::Binary, TagTextEncoding encoding=TagTextEncoding::Latin1)
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
TagType
Specifies the tag type.
Definition: tag.h:20
TagTextEncoding
Specifies the text encoding.
Definition: tagvalue.h:23
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:154