Tag Parser  7.1.0
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
flacstream.cpp
Go to the documentation of this file.
1 #include "./flacstream.h"
2 #include "./flacmetadata.h"
3 
4 #include "../vorbis/vorbiscomment.h"
5 
6 #include "../exceptions.h"
7 #include "../mediafileinfo.h"
8 #include "../mediaformat.h"
9 
10 #include "resources/config.h"
11 
12 #include <c++utilities/io/copy.h>
13 
14 #include <sstream>
15 
16 using namespace std;
17 using namespace IoUtilities;
18 using namespace ConversionUtilities;
19 using namespace ChronoUtilities;
20 
21 namespace TagParser {
22 
33 FlacStream::FlacStream(MediaFileInfo &mediaFileInfo, uint64 startOffset)
34  : AbstractTrack(mediaFileInfo.stream(), startOffset)
35  , m_mediaFileInfo(mediaFileInfo)
36  , m_paddingSize(0)
37  , m_streamOffset(0)
38 {
40 }
41 
47 {
48  if (!m_vorbisComment) {
49  m_vorbisComment = make_unique<VorbisComment>();
50  }
51  return m_vorbisComment.get();
52 }
53 
59 {
60  if (!m_vorbisComment) {
61  return false;
62  }
63  m_vorbisComment.reset();
64  return true;
65 }
66 
68 {
69  static const string context("parsing raw FLAC header");
70  if (!m_istream) {
71  throw NoDataFoundException();
72  }
73 
74  m_istream->seekg(static_cast<streamoff>(m_startOffset), ios_base::beg);
75  char buffer[0x22];
76 
77  // check signature
78  if (m_reader.readUInt32BE() != 0x664C6143) {
79  diag.emplace_back(DiagLevel::Critical, "Signature (fLaC) not found.", context);
80  throw InvalidDataException();
81  }
83 
84  // parse meta data blocks
85  for (FlacMetaDataBlockHeader header; !header.isLast();) {
86  // parse block header
87  m_istream->read(buffer, 4);
88  header.parseHeader(buffer);
89 
90  // remember start offset
91  const auto startOffset = m_istream->tellg();
92 
93  // parse relevant meta data
94  switch (static_cast<FlacMetaDataBlockType>(header.type())) {
96  if (header.dataSize() >= 0x22) {
97  m_istream->read(buffer, 0x22);
98  FlacMetaDataBlockStreamInfo streamInfo;
99  streamInfo.parse(buffer);
100  m_channelCount = streamInfo.channelCount();
101  m_samplingFrequency = streamInfo.samplingFrequency();
102  m_sampleCount = streamInfo.totalSampleCount();
103  m_bitsPerSample = streamInfo.bitsPerSample();
104  m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
105  } else {
106  diag.emplace_back(DiagLevel::Critical, "\"METADATA_BLOCK_STREAMINFO\" is truncated and will be ignored.", context);
107  }
108  break;
109 
111  // parse Vorbis comment
112  // if more than one comment exist, simply thread those comments as one
113  if (!m_vorbisComment) {
114  m_vorbisComment = make_unique<VorbisComment>();
115  }
116  try {
117  m_vorbisComment->parse(*m_istream, header.dataSize(), VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, diag);
118  } catch (const Failure &) {
119  // error is logged via notifications, just continue with the next metadata block
120  }
121  break;
122 
124  try {
125  // parse the cover
126  VorbisCommentField coverField;
127  coverField.setId(m_vorbisComment->fieldId(KnownField::Cover));
128  FlacMetaDataBlockPicture picture(coverField.value());
129  picture.parse(*m_istream, header.dataSize());
130  coverField.setTypeInfo(picture.pictureType());
131 
132  if (coverField.value().isEmpty()) {
133  diag.emplace_back(DiagLevel::Warning, "\"METADATA_BLOCK_PICTURE\" contains no picture.", context);
134  } else {
135  // add the cover to the Vorbis comment
136  if (!m_vorbisComment) {
137  // create one if none exists yet
138  m_vorbisComment = make_unique<VorbisComment>();
139  m_vorbisComment->setVendor(TagValue(APP_NAME " v" APP_VERSION, TagTextEncoding::Utf8));
140  }
141  m_vorbisComment->fields().insert(make_pair(coverField.id(), move(coverField)));
142  }
143 
144  } catch (const TruncatedDataException &) {
145  diag.emplace_back(DiagLevel::Critical, "\"METADATA_BLOCK_PICTURE\" is truncated and will be ignored.", context);
146  }
147  break;
148 
150  m_paddingSize += 4 + header.dataSize();
151  break;
152 
153  default:;
154  }
155 
156  // seek to next block
157  m_istream->seekg(startOffset + static_cast<decltype(startOffset)>(header.dataSize()));
158 
159  // TODO: check first FLAC frame
160  }
161 
162  m_streamOffset = static_cast<uint32>(m_istream->tellg());
163 }
164 
177 uint32 FlacStream::makeHeader(ostream &outputStream, Diagnostics &diag)
178 {
179  istream &originalStream = m_mediaFileInfo.stream();
180  originalStream.seekg(static_cast<streamoff>(m_startOffset + 4));
181  CopyHelper<512> copy;
182 
183  // write signature
184  BE::getBytes(static_cast<uint32>(0x664C6143u), copy.buffer());
185  outputStream.write(copy.buffer(), 4);
186 
187  std::streamoff lastStartOffset = -1;
188 
189  // write meta data blocks which don't need to be adjusted
191  FlacMetaDataBlockHeader lastActuallyWrittenHeader;
192  do {
193  // parse block header
194  originalStream.read(copy.buffer(), 4);
195  header.parseHeader(copy.buffer());
196 
197  // skip/copy block
198  switch (static_cast<FlacMetaDataBlockType>(header.type())) {
202  // skip separately written block
203  originalStream.seekg(header.dataSize(), ios_base::cur);
204  break;
205  default:
206  // copy block which doesn't need to be adjusted
207  originalStream.seekg(-4, ios_base::cur);
208  lastStartOffset = outputStream.tellp();
209  copy.copy(originalStream, outputStream, 4 + header.dataSize());
210  lastActuallyWrittenHeader = header;
211  }
212  } while (!header.isLast());
213 
214  // adjust "isLast" flag if neccassary
215  if (lastStartOffset >= 4
216  && ((!m_vorbisComment && !lastActuallyWrittenHeader.isLast()) || (m_vorbisComment && lastActuallyWrittenHeader.isLast()))) {
217  outputStream.seekp(lastStartOffset);
218  lastActuallyWrittenHeader.setLast(!m_vorbisComment);
219  lastActuallyWrittenHeader.makeHeader(outputStream);
220  originalStream.seekg(lastActuallyWrittenHeader.dataSize(), ios_base::cur);
221  }
222 
223  // write Vorbis comment
224  if (!m_vorbisComment) {
225  return lastStartOffset >= 0 ? static_cast<uint32>(lastStartOffset) : 0;
226  }
227  // leave 4 bytes space for the "METADATA_BLOCK_HEADER"
228  lastStartOffset = outputStream.tellp();
229  outputStream.write(copy.buffer(), 4);
230 
231  // determine cover ID since covers must be written separately
232  const auto coverId = m_vorbisComment->fieldId(KnownField::Cover);
233 
234  // write Vorbis comment
236 
237  // write "METADATA_BLOCK_HEADER"
238  const auto endOffset = outputStream.tellp();
240  auto dataSize(static_cast<uint64>(endOffset) - static_cast<uint64>(lastStartOffset) - 4);
241  if (dataSize > 0xFFFFFF) {
242  dataSize = 0xFFFFFF;
243  diag.emplace_back(DiagLevel::Critical, "Vorbis Comment is too big and will be truncated.", "write Vorbis Comment to FLAC stream");
244  }
245  header.setDataSize(static_cast<uint32>(dataSize));
246  header.setLast(!m_vorbisComment->hasField(coverId));
247  outputStream.seekp(lastStartOffset);
248  header.makeHeader(outputStream);
249  outputStream.seekp(static_cast<streamoff>(dataSize), ios_base::cur);
250  lastActuallyWrittenHeader = header;
251 
252  // write cover fields separately as "METADATA_BLOCK_PICTURE"
253  if (header.isLast()) {
254  return static_cast<uint32>(lastStartOffset);
255  }
257  const auto coverFields = m_vorbisComment->fields().equal_range(coverId);
258  for (auto i = coverFields.first; i != coverFields.second;) {
259  const auto lastCoverStartOffset = outputStream.tellp();
260 
261  try {
262  // write the structure
263  FlacMetaDataBlockPicture pictureBlock(i->second.value());
264  pictureBlock.setPictureType(i->second.typeInfo());
265  header.setDataSize(pictureBlock.requiredSize());
266  header.setLast(++i == coverFields.second);
267  header.makeHeader(outputStream);
268  pictureBlock.make(outputStream);
269 
270  // update variables to handle the "isLast" flag
271  lastStartOffset = lastCoverStartOffset;
272  lastActuallyWrittenHeader = header;
273 
274  } catch (const Failure &) {
275  // we can expect nothing is written in the error case except the FLAC header, so
276  // -> just add an error message
277  diag.emplace_back(DiagLevel::Critical, "Unable to serialize \"METADATA_BLOCK_PICTURE\" from assigned value.",
278  "write \"METADATA_BLOCK_PICTURE\" to FLAC stream");
279  // -> and to recover, go back to where we were before
280  outputStream.seekp(lastCoverStartOffset);
281  }
282  }
283 
284  // adjust "isLast" flag if neccassary
285  if (!lastActuallyWrittenHeader.isLast()) {
286  outputStream.seekp(lastStartOffset);
287  lastActuallyWrittenHeader.setLast(true);
288  lastActuallyWrittenHeader.makeHeader(outputStream);
289  outputStream.seekp(lastActuallyWrittenHeader.dataSize());
290  }
291 
292  return static_cast<uint32>(lastStartOffset);
293 }
294 
299 void FlacStream::makePadding(ostream &stream, uint32 size, bool isLast, Diagnostics &diag)
300 {
301  VAR_UNUSED(diag)
302 
303  // make header
306  header.setLast(isLast);
307  header.setDataSize(size -= 4);
308  header.makeHeader(stream);
309 
310  // write zeroes
311  for (; size; --size) {
312  stream.put(0);
313  }
314 }
315 
316 } // namespace TagParser
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:32
IoUtilities::BinaryReader m_reader
void setTypeInfo(const TypeInfoType &typeInfo)
Sets the type info of the current TagField.
bool removeVorbisComment()
Removes the assigned Vorbis comment if one is assigned; does nothing otherwise.
Definition: flacstream.cpp:58
void parse(std::istream &inputStream, uint32 maxSize)
Parses the FLAC "METADATA_BLOCK_PICTURE".
byte isLast() const
Returns whether this is the last metadata block before the audio blocks.
Definition: flacmetadata.h:63
The FlacMetaDataBlockPicture class is a FLAC "METADATA_BLOCK_PICTURE" parser and maker.
Definition: flacmetadata.h:249
std::ostream & outputStream()
Returns the associated output stream.
STL namespace.
uint32 samplingFrequency() const
Returns the sampling frequency in Hz.
Definition: flacmetadata.h:198
void parse(const char *buffer)
Parses the FLAC "METADATA_BLOCK_STREAMINFO" which is read using the specified iterator.
void setDataSize(uint32 dataSize)
Sets the length in bytes of the meta data (excluding the size of the header itself).
Definition: flacmetadata.h:105
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition: mediafileinfo.h:44
uint64 totalSampleCount() const
Returns the total samples in stream.
Definition: flacmetadata.h:233
bool isEmpty() const
Returns an indication whether an value is assigned.
Definition: tagvalue.h:384
void setPictureType(uint32 pictureType)
Sets the picture type according to the ID3v2 APIC frame.
Definition: flacmetadata.h:290
byte channelCount() const
Returns the number of channels.
Definition: flacmetadata.h:208
Implementation of TagParser::Tag for Vorbis comments.
Definition: vorbiscomment.h:25
uint32 makeHeader(std::ostream &stream, Diagnostics &diag)
Writes the FLAC metadata header to the specified outputStream.
Definition: flacstream.cpp:177
Contains utility classes helping to read and write streams.
void makeHeader(std::ostream &outputStream)
Writes the header to the specified outputStream.
uint64 startOffset() const
Returns the start offset of the track in the associated stream.
ChronoUtilities::TimeSpan m_duration
void internalParseHeader(Diagnostics &diag) override
This method is internally called to parse header information.
Definition: flacstream.cpp:67
uint64 size() const
Returns the size in bytes if known; otherwise returns 0.
The exception that is thrown when the data to be parsed holds no parsable information.
Definition: exceptions.h:18
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
Definition: flacmetadata.h:29
uint32 dataSize() const
Returns the length in bytes of the meta data (excluding the size of the header itself).
Definition: flacmetadata.h:96
VorbisComment * createVorbisComment()
Creates a new Vorbis comment for the stream.
Definition: flacstream.cpp:46
std::istream * m_istream
const IdentifierType & id() const
Returns the id of the current TagField.
byte bitsPerSample() const
Returns the bits per sample.
Definition: flacmetadata.h:220
void setType(FlacMetaDataBlockType type)
Sets the block type.
Definition: flacmetadata.h:88
static void makePadding(std::ostream &stream, uint32 size, bool isLast, Diagnostics &diag)
Writes padding of the specified size to the specified stream.
Definition: flacstream.cpp:299
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
void setLast(byte last)
Sets whether this is the last metadata block before the audio blocks.
Definition: flacmetadata.h:71
IoUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition: basicfileinfo.h:79
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition: abstracttrack.h:39
The VorbisCommentField class is used by VorbisComment to store the fields.
The TagValue class wraps values of different types.
Definition: tagvalue.h:64
byte type() const
Returns the block type.
Definition: flacmetadata.h:80
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
void parseHeader(const char *buffer)
Parses the FLAC "METADATA_BLOCK_HEADER" which is read using the specified iterator.
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:9
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:154
TagValue & value()
Returns the value of the current TagField.
The FlacMetaDataBlockStreamInfo class is a FLAC "METADATA_BLOCK_STREAMINFO" parser.
Definition: flacmetadata.h:110