Tag Parser: flac/flacstream.cpp Source File

Tag Parser 12.5.3 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
25
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{
37 m_mediaType = MediaType::Audio;
38}
39
44 VorbisComment *FlacStream::createVorbisComment()
45{
46 if (!m_vorbisComment) {
47 m_vorbisComment = make_unique<VorbisComment>();
48 }
49 return m_vorbisComment.get();
50}
51
56 bool FlacStream::removeVorbisComment()
57{
58 if (!m_vorbisComment) {
59 return false;
60 }
61 m_vorbisComment.reset();
62 return true;
63}
64
65 void FlacStream::internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress)
66{
67 CPP_UTILITIES_UNUSED(progress)
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 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);
81 throw InvalidDataException();
82 }
83 m_format = GeneralMediaFormat::Flac;
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())) {
96 case FlacMetaDataBlockType::StreamInfo:
97 if (header.dataSize() >= bufferSize) {
98 m_istream->read(buffer, bufferSize);
99 FlacMetaDataBlockStreamInfo streamInfo;
100 streamInfo.parse(std::string_view(buffer, bufferSize));
101 m_channelCount = streamInfo.channelCount();
102 m_samplingFrequency = streamInfo.samplingFrequency();
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
111 case FlacMetaDataBlockType::VorbisComment:
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 auto flags = VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte;
119 if (m_mediaFileInfo.fileHandlingFlags() & MediaFileHandlingFlags::ConvertTotalFields) {
120 flags += VorbisCommentFlags::ConvertTotalFields;
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
128 case FlacMetaDataBlockType::Picture:
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
154 case FlacMetaDataBlockType::Padding:
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
194 FlacMetaDataBlockHeader header;
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())) {
203 case FlacMetaDataBlockType::VorbisComment:
204 case FlacMetaDataBlockType::Picture:
205 case FlacMetaDataBlockType::Padding:
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
239 m_vorbisComment->make(outputStream, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte | VorbisCommentFlags::NoCovers, diag);
240
241 // write "METADATA_BLOCK_HEADER"
242 const auto endOffset = outputStream.tellp();
243 header.setType(FlacMetaDataBlockType::VorbisComment);
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 }
260 header.setType(FlacMetaDataBlockType::Picture);
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
303 void FlacStream::makePadding(ostream &stream, std::uint32_t size, bool isLast, Diagnostics &diag)
304{
305 CPP_UTILITIES_UNUSED(diag)
306
307 // make header
308 FlacMetaDataBlockHeader header;
309 header.setType(FlacMetaDataBlockType::Padding);
310 header.setLast(isLast);
311 header.setDataSize(size -= 4);
312 header.makeHeader(stream);
313
314 // write zeroes
315 MediaFileInfo::writePadding(stream, size);
316}
317
318} // namespace TagParser
TagParser::AbortableProgressFeedback
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
Definition progressfeedback.h:186
TagParser::AbstractTrack::size
std::uint64_t size() const
Returns the size in bytes if known; otherwise returns 0.
Definition abstracttrack.h:394
TagParser::AbstractTrack::m_sampleCount
std::uint64_t m_sampleCount
Definition abstracttrack.h:221
TagParser::AbstractTrack::startOffset
std::uint64_t startOffset() const
Returns the start offset of the track in the associated stream.
Definition abstracttrack.h:316
TagParser::AbstractTrack::m_bitsPerSample
std::uint16_t m_bitsPerSample
Definition abstracttrack.h:215
TagParser::AbstractTrack::AbstractTrack
AbstractTrack(std::istream &inputStream, std::ostream &outputStream, std::uint64_t startOffset)
Constructs a new track.
Definition abstracttrack.cpp:33
TagParser::AbstractTrack::m_channelCount
std::uint16_t m_channelCount
Definition abstracttrack.h:217
TagParser::AbstractTrack::m_duration
CppUtilities::TimeSpan m_duration
Definition abstracttrack.h:207
TagParser::AbstractTrack::m_reader
CppUtilities::BinaryReader m_reader
Definition abstracttrack.h:194
TagParser::AbstractTrack::m_startOffset
std::uint64_t m_startOffset
Definition abstracttrack.h:196
TagParser::AbstractTrack::flags
TrackFlags flags() const
Returns flags (various boolean properties) of this track.
Definition abstracttrack.h:325
TagParser::AbstractTrack::outputStream
std::ostream & outputStream()
Returns the associated output stream.
Definition abstracttrack.h:267
TagParser::AbstractTrack::m_format
MediaFormat m_format
Definition abstracttrack.h:198
TagParser::AbstractTrack::m_istream
std::istream * m_istream
Definition abstracttrack.h:192
TagParser::AbstractTrack::m_samplingFrequency
std::uint32_t m_samplingFrequency
Definition abstracttrack.h:213
TagParser::AbstractTrack::m_mediaType
MediaType m_mediaType
Definition abstracttrack.h:201
TagParser::Diagnostics
The Diagnostics class is a container for DiagMessage.
Definition diagnostics.h:156
TagParser::Failure
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
Definition exceptions.h:11
TagParser::FlacMetaDataBlockHeader
The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
Definition flacmetadata.h:28
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::FlacMetaDataBlockHeader::setType
void setType(FlacMetaDataBlockType type)
Sets the block type.
Definition flacmetadata.h:87
TagParser::FlacMetaDataBlockHeader::type
constexpr std::uint8_t type() const
Returns the block type.
Definition flacmetadata.h:79
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::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::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::FlacMetaDataBlockHeader::parseHeader
void parseHeader(std::string_view buffer)
Parses the FLAC "METADATA_BLOCK_HEADER" which is read using the specified iterator.
Definition flacmetadata.cpp:29
TagParser::FlacMetaDataBlockHeader::makeHeader
void makeHeader(std::ostream &outputStream)
Writes the header to the specified outputStream.
Definition flacmetadata.cpp:40
TagParser::FlacMetaDataBlockPicture
The FlacMetaDataBlockPicture class is a FLAC "METADATA_BLOCK_PICTURE" parser and maker.
Definition flacmetadata.h:248
TagParser::FlacMetaDataBlockPicture::requiredSize
std::uint32_t requiredSize() const
Returns the number of bytes make() will write.
Definition flacmetadata.cpp:112
TagParser::FlacMetaDataBlockPicture::make
void make(std::ostream &outputStream)
Makes the FLAC "METADATA_BLOCK_PICTURE".
Definition flacmetadata.cpp:125
TagParser::FlacMetaDataBlockPicture::setPictureType
void setPictureType(std::uint32_t pictureType)
Sets the picture type according to the ID3v2 APIC frame.
Definition flacmetadata.h:289
TagParser::FlacMetaDataBlockPicture::parse
void parse(std::istream &inputStream, std::uint32_t maxSize)
Parses the FLAC "METADATA_BLOCK_PICTURE".
Definition flacmetadata.cpp:83
TagParser::FlacMetaDataBlockPicture::pictureType
std::uint32_t pictureType() const
Returns the picture type according to the ID3v2 APIC frame.
Definition flacmetadata.h:281
TagParser::FlacMetaDataBlockStreamInfo
The FlacMetaDataBlockStreamInfo class is a FLAC "METADATA_BLOCK_STREAMINFO" parser.
Definition flacmetadata.h:109
TagParser::FlacMetaDataBlockStreamInfo::totalSampleCount
constexpr std::uint64_t totalSampleCount() const
Returns the total samples in stream.
Definition flacmetadata.h:232
TagParser::FlacMetaDataBlockStreamInfo::samplingFrequency
constexpr std::uint32_t samplingFrequency() const
Returns the sampling frequency in Hz.
Definition flacmetadata.h:197
TagParser::FlacMetaDataBlockStreamInfo::bitsPerSample
constexpr std::uint8_t bitsPerSample() const
Returns the bits per sample.
Definition flacmetadata.h:219
TagParser::FlacMetaDataBlockStreamInfo::channelCount
constexpr std::uint8_t channelCount() const
Returns the number of channels.
Definition flacmetadata.h:207
TagParser::FlacMetaDataBlockStreamInfo::parse
void parse(std::string_view buffer)
Parses the FLAC "METADATA_BLOCK_STREAMINFO" which is read using the specified iterator.
Definition flacmetadata.cpp:58
TagParser::FlacStream::createVorbisComment
VorbisComment * createVorbisComment()
Creates a new Vorbis comment for the stream.
Definition flacstream.cpp:44
TagParser::FlacStream::FlacStream
FlacStream(MediaFileInfo &mediaFileInfo, std::uint64_t startOffset)
Constructs a new track for the specified mediaFileInfo at the specified startOffset.
Definition flacstream.cpp:31
TagParser::FlacStream::internalParseHeader
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
This method is internally called to parse header information.
Definition flacstream.cpp:65
TagParser::FlacStream::removeVorbisComment
bool removeVorbisComment()
Removes the assigned Vorbis comment if one is assigned; does nothing otherwise.
Definition flacstream.cpp:56
TagParser::FlacStream::makeHeader
std::streamoff makeHeader(std::ostream &stream, Diagnostics &diag)
Writes the FLAC metadata header to the specified outputStream.
Definition flacstream.cpp:181
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:303
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::MediaFileInfo
The MediaFileInfo class allows to read and write tag information providing a container/tag format ind...
Definition mediafileinfo.h:83
TagParser::MediaFileInfo::writePadding
static void writePadding(std::ostream &outputStream, uint64_t size)
Writes the specified number of zeroes to outputStream.
Definition mediafileinfo.cpp:1399
TagParser::NoDataFoundException
The exception that is thrown when the data to be parsed holds no parsable information (e....
Definition exceptions.h:18
TagParser::TagField::setTypeInfo
void setTypeInfo(const TypeInfoType &typeInfo)
Sets the type info of the current TagField.
Definition generictagfield.h:196
TagParser::TagField::id
IdentifierType & id()
Returns the id of the current TagField.
Definition generictagfield.h:116
TagParser::TagField::setId
void setId(const IdentifierType &id)
Sets the id of the current Tag Field.
Definition generictagfield.h:140
TagParser::TagField::value
TagValue & value()
Returns the value of the current TagField.
Definition generictagfield.h:156
TagParser::TagValue
The TagValue class wraps values of different types.
Definition tagvalue.h:147
TagParser::TagValue::isEmpty
bool isEmpty() const
Returns whether no or an empty value is assigned.
Definition tagvalue.h:490
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::VorbisCommentField
The VorbisCommentField class is used by VorbisComment to store the fields.
Definition vorbiscommentfield.h:46
TagParser::VorbisComment
Implementation of TagParser::Tag for Vorbis comments.
Definition vorbiscomment.h:27
exceptions.h
flacmetadata.h
flacstream.h
mediafileinfo.h
mediaformat.h
CppUtilities
Definition abstractcontainer.h:16
TagParser
Contains all classes and functions of the TagInfo library.
Definition aaccodebook.h:10
TagParser::KnownField::Cover
@ Cover
Definition tag.h:61
TagParser::FlacMetaDataBlockType
FlacMetaDataBlockType
The FlacMetaDataBlockType enum specifies the type of FlacMetaDataBlockHeader.
Definition flacmetadata.h:16
TagParser::FlacMetaDataBlockType::Picture
@ Picture
Definition flacmetadata.h:16
TagParser::FlacMetaDataBlockType::Padding
@ Padding
Definition flacmetadata.h:16
TagParser::FlacMetaDataBlockType::VorbisComment
@ VorbisComment
Definition flacmetadata.h:16
TagParser::FlacMetaDataBlockType::StreamInfo
@ StreamInfo
Definition flacmetadata.h:16
TagParser::TagTextEncoding::Utf8
@ Utf8
Definition tagvalue.h:31
TagParser::DiagLevel::Warning
@ Warning
Definition diagnostics.h:20
TagParser::DiagLevel::Critical
@ Critical
Definition diagnostics.h:21
TagParser::MediaType::Audio
@ Audio
Definition mediaformat.h:16
TagParser::GeneralMediaFormat::Flac
@ Flac
Definition mediaformat.h:51
TagParser::MediaFileHandlingFlags::ConvertTotalFields
@ ConvertTotalFields
Definition mediafileinfo.h:69
TagParser::VorbisCommentFlags::NoCovers
@ NoCovers
Definition vorbiscommentfield.h:22
TagParser::VorbisCommentFlags::ConvertTotalFields
@ ConvertTotalFields
Definition vorbiscommentfield.h:23
TagParser::VorbisCommentFlags::NoSignature
@ NoSignature
Definition vorbiscommentfield.h:20
TagParser::VorbisCommentFlags::NoFramingByte
@ NoFramingByte
Definition vorbiscommentfield.h:21
vorbiscomment.h