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