Tag Parser 10.3.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
16using namespace std;
17using namespace CppUtilities;
18
19namespace TagParser {
20
31FlacStream::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 CPP_UTILITIES_UNUSED(progress)
68
69 static const string context("parsing raw FLAC header");
70 if (!m_istream) {
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);
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);
99 streamInfo.parse(buffer);
100 m_channelCount = streamInfo.channelCount();
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<std::uint32_t>(m_istream->tellg());
163}
164
176std::streamoff FlacStream::makeHeader(ostream &outputStream, Diagnostics &diag)
177{
178 istream &originalStream = m_mediaFileInfo.stream();
179 originalStream.seekg(static_cast<streamoff>(m_startOffset + 4));
180 CopyHelper<512> copy;
181
182 // write signature
183 BE::getBytes(static_cast<std::uint32_t>(0x664C6143u), copy.buffer());
184 outputStream.write(copy.buffer(), 4);
185
186 std::streamoff lastStartOffset = -1;
187
188 // write meta data blocks which don't need to be adjusted
190 FlacMetaDataBlockHeader lastActuallyWrittenHeader;
191 do {
192 // parse block header
193 originalStream.read(copy.buffer(), 4);
194 header.parseHeader(copy.buffer());
195
196 // skip/copy block
197 switch (static_cast<FlacMetaDataBlockType>(header.type())) {
201 // skip separately written block
202 originalStream.seekg(header.dataSize(), ios_base::cur);
203 break;
204 default:
205 // copy block which doesn't need to be adjusted
206 originalStream.seekg(-4, ios_base::cur);
207 lastStartOffset = outputStream.tellp();
208 copy.copy(originalStream, outputStream, 4 + header.dataSize());
209 lastActuallyWrittenHeader = header;
210 }
211 } while (!header.isLast());
212
213 // adjust "isLast" flag if neccassary
214 if (lastStartOffset >= 4
215 && ((!m_vorbisComment && !lastActuallyWrittenHeader.isLast()) || (m_vorbisComment && lastActuallyWrittenHeader.isLast()))) {
216 outputStream.seekp(lastStartOffset);
217 lastActuallyWrittenHeader.setLast(!m_vorbisComment);
218 lastActuallyWrittenHeader.makeHeader(outputStream);
219 originalStream.seekg(lastActuallyWrittenHeader.dataSize(), ios_base::cur);
220 }
221
222 // write Vorbis comment
223 if (!m_vorbisComment) {
224 return lastStartOffset >= 0 ? lastStartOffset : 0;
225 }
226 // leave 4 bytes space for the "METADATA_BLOCK_HEADER"
227 lastStartOffset = outputStream.tellp();
228 outputStream.write(copy.buffer(), 4);
229
230 // determine cover ID since covers must be written separately
231 const auto coverId = m_vorbisComment->fieldId(KnownField::Cover);
232
233 // write Vorbis comment
235
236 // write "METADATA_BLOCK_HEADER"
237 const auto endOffset = outputStream.tellp();
239 auto dataSize(static_cast<std::uint64_t>(endOffset) - static_cast<std::uint64_t>(lastStartOffset) - 4);
240 if (dataSize > 0xFFFFFF) {
241 dataSize = 0xFFFFFF;
242 diag.emplace_back(DiagLevel::Critical, "Vorbis Comment is too big and will be truncated.", "write Vorbis Comment to FLAC stream");
243 }
244 header.setDataSize(static_cast<std::uint32_t>(dataSize));
245 header.setLast(!m_vorbisComment->hasField(coverId));
246 outputStream.seekp(lastStartOffset);
247 header.makeHeader(outputStream);
248 outputStream.seekp(static_cast<streamoff>(dataSize), ios_base::cur);
249 lastActuallyWrittenHeader = header;
250
251 // write cover fields separately as "METADATA_BLOCK_PICTURE"
252 if (header.isLast()) {
253 return lastStartOffset;
254 }
256 const auto coverFields = m_vorbisComment->fields().equal_range(coverId);
257 for (auto i = coverFields.first; i != coverFields.second;) {
258 const auto lastCoverStartOffset = outputStream.tellp();
259
260 try {
261 // write the structure
262 FlacMetaDataBlockPicture pictureBlock(i->second.value());
263 pictureBlock.setPictureType(i->second.typeInfo());
264 header.setDataSize(pictureBlock.requiredSize());
265 header.setLast(++i == coverFields.second);
266 header.makeHeader(outputStream);
267 pictureBlock.make(outputStream);
268
269 // update variables to handle the "isLast" flag
270 lastStartOffset = lastCoverStartOffset;
271 lastActuallyWrittenHeader = header;
272
273 } catch (const Failure &) {
274 // we can expect nothing is written in the error case except the FLAC header, so
275 // -> just add an error message
276 diag.emplace_back(DiagLevel::Critical, "Unable to serialize \"METADATA_BLOCK_PICTURE\" from assigned value.",
277 "write \"METADATA_BLOCK_PICTURE\" to FLAC stream");
278 // -> and to recover, go back to where we were before
279 outputStream.seekp(lastCoverStartOffset);
280 }
281 }
282
283 // adjust "isLast" flag if neccassary
284 if (!lastActuallyWrittenHeader.isLast()) {
285 outputStream.seekp(lastStartOffset);
286 lastActuallyWrittenHeader.setLast(true);
287 lastActuallyWrittenHeader.makeHeader(outputStream);
288 outputStream.seekp(lastActuallyWrittenHeader.dataSize());
289 }
290
291 return lastStartOffset;
292}
293
298void FlacStream::makePadding(ostream &stream, std::uint32_t size, bool isLast, Diagnostics &diag)
299{
300 CPP_UTILITIES_UNUSED(diag)
301
302 // make header
305 header.setLast(isLast);
306 header.setDataSize(size -= 4);
307 header.makeHeader(stream);
308
309 // write zeroes
310 for (; size; --size) {
311 stream.put(0);
312 }
313}
314
315} // namespace TagParser
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
The AbstractTrack class parses and stores technical information about video, audio and other kinds of...
Definition: abstracttrack.h:65
std::uint64_t size() const
Returns the size in bytes if known; otherwise returns 0.
std::uint64_t m_sampleCount
std::uint64_t startOffset() const
Returns the start offset of the track in the associated stream.
std::uint16_t m_bitsPerSample
std::uint16_t m_channelCount
CppUtilities::TimeSpan m_duration
CppUtilities::BinaryReader m_reader
std::uint64_t m_startOffset
std::ostream & outputStream()
Returns the associated output stream.
std::istream * m_istream
std::uint32_t m_samplingFrequency
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
Definition: basicfileinfo.h:85
The Diagnostics class is a container for DiagMessage.
Definition: diagnostics.h:156
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition: exceptions.h:11
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
Definition: flacmetadata.h:28
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
void setType(FlacMetaDataBlockType type)
Sets the block type.
Definition: flacmetadata.h:87
constexpr std::uint8_t type() const
Returns the block type.
Definition: flacmetadata.h:79
void setLast(std::uint8_t last)
Sets whether this is the last metadata block before the audio blocks.
Definition: flacmetadata.h:70
constexpr std::uint8_t isLast() const
Returns whether this is the last metadata block before the audio blocks.
Definition: flacmetadata.h:62
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
void parseHeader(std::string_view buffer)
Parses the FLAC "METADATA_BLOCK_HEADER" which is read using the specified iterator.
void makeHeader(std::ostream &outputStream)
Writes the header to the specified outputStream.
The FlacMetaDataBlockPicture class is a FLAC "METADATA_BLOCK_PICTURE" parser and maker.
Definition: flacmetadata.h:248
std::uint32_t requiredSize() const
Returns the number of bytes make() will write.
void make(std::ostream &outputStream)
Makes the FLAC "METADATA_BLOCK_PICTURE".
void setPictureType(std::uint32_t pictureType)
Sets the picture type according to the ID3v2 APIC frame.
Definition: flacmetadata.h:289
void parse(std::istream &inputStream, std::uint32_t maxSize)
Parses the FLAC "METADATA_BLOCK_PICTURE".
std::uint32_t pictureType() const
Returns the picture type according to the ID3v2 APIC frame.
Definition: flacmetadata.h:281
The FlacMetaDataBlockStreamInfo class is a FLAC "METADATA_BLOCK_STREAMINFO" parser.
Definition: flacmetadata.h:109
constexpr std::uint64_t totalSampleCount() const
Returns the total samples in stream.
Definition: flacmetadata.h:232
constexpr std::uint32_t samplingFrequency() const
Returns the sampling frequency in Hz.
Definition: flacmetadata.h:197
constexpr std::uint8_t bitsPerSample() const
Returns the bits per sample.
Definition: flacmetadata.h:219
constexpr std::uint8_t channelCount() const
Returns the number of channels.
Definition: flacmetadata.h:207
void parse(std::string_view buffer)
Parses the FLAC "METADATA_BLOCK_STREAMINFO" which is read using the specified iterator.
VorbisComment * createVorbisComment()
Creates a new Vorbis comment for the stream.
Definition: flacstream.cpp:44
FlacStream(MediaFileInfo &mediaFileInfo, std::uint64_t startOffset)
Constructs a new track for the specified mediaFileInfo at the specified startOffset.
Definition: flacstream.cpp:31
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
This method is internally called to parse header information.
Definition: flacstream.cpp:65
bool removeVorbisComment()
Removes the assigned Vorbis comment if one is assigned; does nothing otherwise.
Definition: flacstream.cpp:56
std::streamoff makeHeader(std::ostream &stream, Diagnostics &diag)
Writes the FLAC metadata header to the specified outputStream.
Definition: flacstream.cpp:176
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:298
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
Definition: exceptions.h:25
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition: mediafileinfo.h:75
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition: exceptions.h:18
void setTypeInfo(const TypeInfoType &typeInfo)
Sets the type info of the current TagField.
IdentifierType & id()
Returns the id of the current TagField.
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
TagValue & value()
Returns the value of the current TagField.
The TagValue class wraps values of different types.
Definition: tagvalue.h:95
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition: tagvalue.h:527
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
Definition: exceptions.h:39
The VorbisCommentField class is used by VorbisComment to store the fields.
Implementation of TagParser::Tag for Vorbis comments.
Definition: vorbiscomment.h:25
Contains all classes and functions of the TagInfo library.
Definition: aaccodebook.h:10
FlacMetaDataBlockType
The FlacMetaDataBlockType enum specifies the type of FlacMetaDataBlockHeader.
Definition: flacmetadata.h:16