Tag Parser 12.1.0
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
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 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 {
118 m_vorbisComment->parse(*m_istream, header.dataSize(), VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, diag);
119 } catch (const Failure &) {
120 // error is logged via notifications, just continue with the next metadata block
121 }
122 break;
123
125 try {
126 // parse the cover
127 VorbisCommentField coverField;
128 coverField.setId(m_vorbisComment->fieldId(KnownField::Cover));
129 FlacMetaDataBlockPicture picture(coverField.value());
130 picture.parse(*m_istream, header.dataSize());
131 coverField.setTypeInfo(picture.pictureType());
132
133 if (coverField.value().isEmpty()) {
134 diag.emplace_back(DiagLevel::Warning, "\"METADATA_BLOCK_PICTURE\" contains no picture.", context);
135 } else {
136 // add the cover to the Vorbis comment
137 if (!m_vorbisComment) {
138 // create one if none exists yet
139 m_vorbisComment = make_unique<VorbisComment>();
140 m_vorbisComment->setVendor(TagValue(APP_NAME " v" APP_VERSION, TagTextEncoding::Utf8));
141 }
142 m_vorbisComment->fields().insert(make_pair(coverField.id(), std::move(coverField)));
143 }
144
145 } catch (const TruncatedDataException &) {
146 diag.emplace_back(DiagLevel::Critical, "\"METADATA_BLOCK_PICTURE\" is truncated and will be ignored.", context);
147 }
148 break;
149
151 m_paddingSize += 4 + header.dataSize();
152 break;
153
154 default:;
155 }
156
157 // seek to next block
158 m_istream->seekg(startOffset + static_cast<decltype(startOffset)>(header.dataSize()));
159
160 // TODO: check first FLAC frame
161 }
162
163 m_streamOffset = static_cast<std::uint32_t>(m_istream->tellg());
164}
165
177std::streamoff 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<std::uint32_t>(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(std::string_view(copy.buffer(), 4));
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 ? 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<std::uint64_t>(endOffset) - static_cast<std::uint64_t>(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<std::uint32_t>(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 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 lastStartOffset;
293}
294
299void FlacStream::makePadding(ostream &stream, std::uint32_t size, bool isLast, Diagnostics &diag)
300{
301 CPP_UTILITIES_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 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
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...
Definition exceptions.h:11
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...
Definition exceptions.h:25
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
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:147
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 ...
Definition exceptions.h:39
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.