Tag Parser 12.3.1
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags
Loading...
Searching...
No Matches
flacstream.cpp
Go to the documentation of this file.
1#include "./flacstream.h"
2#include "./flacmetadata.h"
3
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 constexpr auto bufferSize = 0x22;
76 char buffer[bufferSize];
77
78 // check signature
79 if (m_reader.readUInt32BE() != 0x664C6143) {
80 diag.emplace_back(DiagLevel::Critical, "Signature (fLaC) not found.", context);
82 }
84
85 // parse meta data blocks
86 for (FlacMetaDataBlockHeader header; !header.isLast();) {
87 // parse block header
88 m_istream->read(buffer, 4);
89 header.parseHeader(std::string_view(buffer, 4));
90
91 // remember start offset
92 const auto startOffset = m_istream->tellg();
93
94 // parse relevant meta data
95 switch (static_cast<FlacMetaDataBlockType>(header.type())) {
97 if (header.dataSize() >= bufferSize) {
98 m_istream->read(buffer, bufferSize);
100 streamInfo.parse(std::string_view(buffer, bufferSize));
101 m_channelCount = streamInfo.channelCount();
103 m_sampleCount = streamInfo.totalSampleCount();
104 m_bitsPerSample = streamInfo.bitsPerSample();
105 m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
106 } else {
107 diag.emplace_back(DiagLevel::Critical, "\"METADATA_BLOCK_STREAMINFO\" is truncated and will be ignored.", context);
108 }
109 break;
110
112 // parse Vorbis comment
113 // if more than one comment exist, simply thread those comments as one
114 if (!m_vorbisComment) {
115 m_vorbisComment = make_unique<VorbisComment>();
116 }
117 try {
121 }
122 m_vorbisComment->parse(*m_istream, header.dataSize(), flags, diag);
123 } catch (const Failure &) {
124 // error is logged via notifications, just continue with the next metadata block
125 }
126 break;
127
129 try {
130 // parse the cover
131 VorbisCommentField coverField;
132 coverField.setId(m_vorbisComment->fieldId(KnownField::Cover));
133 FlacMetaDataBlockPicture picture(coverField.value());
134 picture.parse(*m_istream, header.dataSize());
135 coverField.setTypeInfo(picture.pictureType());
136
137 if (coverField.value().isEmpty()) {
138 diag.emplace_back(DiagLevel::Warning, "\"METADATA_BLOCK_PICTURE\" contains no picture.", context);
139 } else {
140 // add the cover to the Vorbis comment
141 if (!m_vorbisComment) {
142 // create one if none exists yet
143 m_vorbisComment = make_unique<VorbisComment>();
144 m_vorbisComment->setVendor(TagValue(APP_NAME " v" APP_VERSION, TagTextEncoding::Utf8));
145 }
146 m_vorbisComment->fields().insert(make_pair(coverField.id(), std::move(coverField)));
147 }
148
149 } catch (const TruncatedDataException &) {
150 diag.emplace_back(DiagLevel::Critical, "\"METADATA_BLOCK_PICTURE\" is truncated and will be ignored.", context);
151 }
152 break;
153
155 m_paddingSize += 4 + header.dataSize();
156 break;
157
158 default:;
159 }
160
161 // seek to next block
162 m_istream->seekg(startOffset + static_cast<decltype(startOffset)>(header.dataSize()));
163
164 // TODO: check first FLAC frame
165 }
166
167 m_streamOffset = static_cast<std::uint32_t>(m_istream->tellg());
168}
169
181std::streamoff FlacStream::makeHeader(ostream &outputStream, Diagnostics &diag)
182{
183 istream &originalStream = m_mediaFileInfo.stream();
184 originalStream.seekg(static_cast<streamoff>(m_startOffset + 4));
185 CopyHelper<512> copy;
186
187 // write signature
188 BE::getBytes(static_cast<std::uint32_t>(0x664C6143u), copy.buffer());
189 outputStream.write(copy.buffer(), 4);
190
191 std::streamoff lastStartOffset = -1;
192
193 // write meta data blocks which don't need to be adjusted
195 FlacMetaDataBlockHeader lastActuallyWrittenHeader;
196 do {
197 // parse block header
198 originalStream.read(copy.buffer(), 4);
199 header.parseHeader(std::string_view(copy.buffer(), 4));
200
201 // skip/copy block
202 switch (static_cast<FlacMetaDataBlockType>(header.type())) {
206 // skip separately written block
207 originalStream.seekg(header.dataSize(), ios_base::cur);
208 break;
209 default:
210 // copy block which doesn't need to be adjusted
211 originalStream.seekg(-4, ios_base::cur);
212 lastStartOffset = outputStream.tellp();
213 copy.copy(originalStream, outputStream, 4 + header.dataSize());
214 lastActuallyWrittenHeader = header;
215 }
216 } while (!header.isLast());
217
218 // adjust "isLast" flag if neccassary
219 if (lastStartOffset >= 4
220 && ((!m_vorbisComment && !lastActuallyWrittenHeader.isLast()) || (m_vorbisComment && lastActuallyWrittenHeader.isLast()))) {
221 outputStream.seekp(lastStartOffset);
222 lastActuallyWrittenHeader.setLast(!m_vorbisComment);
223 lastActuallyWrittenHeader.makeHeader(outputStream);
224 originalStream.seekg(lastActuallyWrittenHeader.dataSize(), ios_base::cur);
225 }
226
227 // write Vorbis comment
228 if (!m_vorbisComment) {
229 return lastStartOffset >= 0 ? lastStartOffset : 0;
230 }
231 // leave 4 bytes space for the "METADATA_BLOCK_HEADER"
232 lastStartOffset = outputStream.tellp();
233 outputStream.write(copy.buffer(), 4);
234
235 // determine cover ID since covers must be written separately
236 const auto coverId = m_vorbisComment->fieldId(KnownField::Cover);
237
238 // write Vorbis comment
240
241 // write "METADATA_BLOCK_HEADER"
242 const auto endOffset = outputStream.tellp();
244 auto dataSize(static_cast<std::uint64_t>(endOffset) - static_cast<std::uint64_t>(lastStartOffset) - 4);
245 if (dataSize > 0xFFFFFF) {
246 dataSize = 0xFFFFFF;
247 diag.emplace_back(DiagLevel::Critical, "Vorbis Comment is too big and will be truncated.", "write Vorbis Comment to FLAC stream");
248 }
249 header.setDataSize(static_cast<std::uint32_t>(dataSize));
250 header.setLast(!m_vorbisComment->hasField(coverId));
251 outputStream.seekp(lastStartOffset);
252 header.makeHeader(outputStream);
253 outputStream.seekp(static_cast<streamoff>(dataSize), ios_base::cur);
254 lastActuallyWrittenHeader = header;
255
256 // write cover fields separately as "METADATA_BLOCK_PICTURE"
257 if (header.isLast()) {
258 return lastStartOffset;
259 }
261 const auto coverFields = m_vorbisComment->fields().equal_range(coverId);
262 for (auto i = coverFields.first; i != coverFields.second;) {
263 const auto lastCoverStartOffset = outputStream.tellp();
264
265 try {
266 // write the structure
267 FlacMetaDataBlockPicture pictureBlock(i->second.value());
268 pictureBlock.setPictureType(i->second.typeInfo());
269 header.setDataSize(pictureBlock.requiredSize());
270 header.setLast(++i == coverFields.second);
271 header.makeHeader(outputStream);
272 pictureBlock.make(outputStream);
273
274 // update variables to handle the "isLast" flag
275 lastStartOffset = lastCoverStartOffset;
276 lastActuallyWrittenHeader = header;
277
278 } catch (const Failure &) {
279 // we can expect nothing is written in the error case except the FLAC header, so
280 // -> just add an error message
281 diag.emplace_back(DiagLevel::Critical, "Unable to serialize \"METADATA_BLOCK_PICTURE\" from assigned value.",
282 "write \"METADATA_BLOCK_PICTURE\" to FLAC stream");
283 // -> and to recover, go back to where we were before
284 outputStream.seekp(lastCoverStartOffset);
285 }
286 }
287
288 // adjust "isLast" flag if neccassary
289 if (!lastActuallyWrittenHeader.isLast()) {
290 outputStream.seekp(lastStartOffset);
291 lastActuallyWrittenHeader.setLast(true);
292 lastActuallyWrittenHeader.makeHeader(outputStream);
293 outputStream.seekp(lastActuallyWrittenHeader.dataSize());
294 }
295
296 return lastStartOffset;
297}
298
303void FlacStream::makePadding(ostream &stream, std::uint32_t size, bool isLast, Diagnostics &diag)
304{
305 CPP_UTILITIES_UNUSED(diag)
306
307 // make header
310 header.setLast(isLast);
311 header.setDataSize(size -= 4);
312 header.makeHeader(stream);
313
314 // write zeroes
316}
317
318} // 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...
std::uint64_t size() const
Returns the size in bytes if known; otherwise returns 0.
std::uint64_t startOffset() const
Returns the start offset of the track in the associated stream.
std::uint16_t m_bitsPerSample
CppUtilities::TimeSpan m_duration
CppUtilities::BinaryReader m_reader
TrackFlags flags() const
Returns flags (various boolean properties) of this track.
std::ostream & outputStream()
Returns the associated output stream.
std::uint32_t m_samplingFrequency
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
The Diagnostics class is a container for DiagMessage.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
constexpr std::uint32_t dataSize() const
Returns the length in bytes of the meta data (excluding the size of the header itself).
void setType(FlacMetaDataBlockType type)
Sets the block type.
constexpr std::uint8_t type() const
Returns the block type.
void setLast(std::uint8_t last)
Sets whether this is the last metadata block before the audio blocks.
constexpr std::uint8_t isLast() const
Returns whether this is the last metadata block before the audio blocks.
void setDataSize(std::uint32_t dataSize)
Sets the length in bytes of the meta data (excluding the size of the header itself).
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.
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.
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.
The FlacMetaDataBlockStreamInfo class is a FLAC "METADATA_BLOCK_STREAMINFO" parser.
constexpr std::uint64_t totalSampleCount() const
Returns the total samples in stream.
constexpr std::uint32_t samplingFrequency() const
Returns the sampling frequency in Hz.
constexpr std::uint8_t bitsPerSample() const
Returns the bits per sample.
constexpr std::uint8_t channelCount() const
Returns the number of channels.
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.
FlacStream(MediaFileInfo &mediaFileInfo, std::uint64_t startOffset)
Constructs a new track for the specified mediaFileInfo at the specified startOffset.
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
This method is internally called to parse header information.
bool removeVorbisComment()
Removes the assigned Vorbis comment if one is assigned; does nothing otherwise.
std::streamoff makeHeader(std::ostream &stream, Diagnostics &diag)
Writes the FLAC metadata header to the specified outputStream.
static void makePadding(std::ostream &stream, std::uint32_t size, bool isLast, Diagnostics &diag)
Writes padding of the specified size to the specified stream.
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
MediaFileHandlingFlags fileHandlingFlags()
Returns the currently configured file handling flags.
static void writePadding(std::ostream &outputStream, uint64_t size)
Writes the specified number of zeroes to outputStream.
The exception that is thrown when the data to be parsed holds no parsable information (e....
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.
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition tagvalue.h:490
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
The VorbisCommentField class is used by VorbisComment to store the fields.
Implementation of TagParser::Tag for Vorbis comments.
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
FlacMetaDataBlockType
The FlacMetaDataBlockType enum specifies the type of FlacMetaDataBlockHeader.