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