Add support for raw FLAC streams

This commit is contained in:
Martchus 2016-05-16 20:56:53 +02:00
parent bbafd16dcc
commit a84ac37dbe
25 changed files with 739 additions and 142 deletions

View File

@ -36,6 +36,7 @@ set(HEADER_FILES
opus/opusidentificationheader.h
flac/flactooggmappingheader.h
flac/flacmetadata.h
flac/flacstream.h
positioninset.h
signature.h
size.h
@ -109,6 +110,7 @@ set(SRC_FILES
opus/opusidentificationheader.cpp
flac/flactooggmappingheader.cpp
flac/flacmetadata.cpp
flac/flacstream.cpp
signature.cpp
statusprovider.cpp
tag.cpp

View File

@ -1,13 +1,14 @@
# Tag Parser
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis and Matroska tags.
C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags.
## Supported formats
The tag library can read and write the following tag formats:
- iTunes-style MP4 tags (MP4-DASH is supported)
- iTunes-style MP4/M4A tags (MP4-DASH is supported)
- ID3v1 and ID3v2 tags
- conversion between ID3v1 and different versions of ID3v2
- conversion between ID3v1 and different versions of ID3v2 is possible
- Vorbis, Opus and FLAC comments in Ogg streams
- cover art via "METADATA_BLOCK_PICTURE" is supported
- Vorbis comments and "METADATA_BLOCK_PICTURE" in raw FLAC streams
- Matroska/WebM tags and attachments
## File layout options
@ -23,7 +24,8 @@ or appending the tag. Usage of padding can be configured:
However, it is also possible to force rewriting the entire file.
Taking advantage of padding is currently not supported when dealing with Ogg streams.
Taking advantage of padding is currently not supported when dealing with Ogg streams (it is supported when
dealing with raw FLAC streams).
## Additional features
The library can also display technical information such as the ID, format, language, bitrate,

View File

@ -13,7 +13,7 @@
#include <c++utilities/chrono/datetime.h>
#include <c++utilities/chrono/timespan.h>
#include <iostream>
#include <iosfwd>
#include <string>
namespace Media {
@ -35,7 +35,8 @@ enum class TrackType
Mp4Track, /**< The track is a Media::Mp4Track. */
WaveAudioStream, /**< The track is a Media::WaveAudioStream. */
OggStream, /**< The track is a Media::OggStream. */
AdtsStream /**< The track is a Media::AdtsStream. */
AdtsStream, /**< The track is a Media::AdtsStream. */
FlacStream, /**< The track is a Media::FlacStream. */
};
class LIB_EXPORT AbstractTrack : public StatusProvider

View File

@ -64,6 +64,16 @@ public:
virtual const char *what() const USE_NOTHROW;
};
/*!
* \brief Throws TruncatedDataException() if the specified \a sizeDenotation exceeds maxSize; otherwise maxSize is reduced by \a sizeDenotation.
*/
#define CHECK_MAX_SIZE(sizeDenotation) \
if(maxSize < sizeDenotation) { \
throw TruncatedDataException(); \
} else { \
maxSize -= sizeDenotation; \
}
}
#endif // MEDIA_EXCEPTIONS_H

View File

@ -54,7 +54,7 @@ void FlacMetaDataBlockHeader::makeHeader(std::ostream &outputStream)
/*!
* \brief Parses the FLAC "METADATA_BLOCK_STREAMINFO" which is read using the specified \a iterator.
* \remarks The specified \a buffer must be at least 34 bytes long.
* \remarks The specified \a buffer must be at least 0x22 bytes long.
*/
void FlacMetaDataBlockStreamInfo::parse(const char *buffer)
{
@ -78,18 +78,24 @@ void FlacMetaDataBlockStreamInfo::parse(const char *buffer)
/*!
* \brief Parses the FLAC "METADATA_BLOCK_PICTURE".
*
* \a maxSize specifies the maximum size of the structure.
*/
void FlacMetaDataBlockPicture::parse(istream &inputStream)
void FlacMetaDataBlockPicture::parse(istream &inputStream, uint32 maxSize)
{
CHECK_MAX_SIZE(32);
BinaryReader reader(&inputStream);
m_pictureType = reader.readUInt32BE();
auto size = reader.readUInt32BE();
uint32 size = reader.readUInt32BE();
CHECK_MAX_SIZE(size);
m_value.setMimeType(reader.readString(size));
size = reader.readUInt32BE();
CHECK_MAX_SIZE(size);
m_value.setDescription(reader.readString(size));
// skip width, height, color depth, number of colors used
inputStream.seekg(4 * 4, ios_base::cur);
size = reader.readUInt32BE();
CHECK_MAX_SIZE(size);
auto data = make_unique<char[]>(size);
inputStream.read(data.get(), size);
m_value.assignData(move(data), size, TagDataType::Picture);

View File

@ -66,6 +66,7 @@ inline FlacMetaDataBlockHeader::FlacMetaDataBlockHeader() :
/*!
* \brief Returns whether this is the last metadata block before the audio blocks.
* \remarks The default value is 0/false.
*/
inline byte FlacMetaDataBlockHeader::isLast() const
{
@ -258,7 +259,7 @@ class LIB_EXPORT FlacMetaDataBlockPicture
public:
FlacMetaDataBlockPicture(TagValue &tagValue);
void parse(std::istream &inputStream);
void parse(std::istream &inputStream, uint32 maxSize);
uint32 requiredSize() const;
void make(std::ostream &outputStream);

253
flac/flacstream.cpp Normal file
View File

@ -0,0 +1,253 @@
#include "./flacstream.h"
#include "./flacmetadata.h"
#include "../vorbis/vorbiscomment.h"
#include "../exceptions.h"
#include "../mediafileinfo.h"
#include "../mediaformat.h"
#include "resources/config.h"
#include <c++utilities/io/copy.h>
#include <sstream>
using namespace std;
using namespace IoUtilities;
using namespace ConversionUtilities;
using namespace ChronoUtilities;
namespace Media {
/*!
* \class Media::FlacStream
* \brief Implementation of Media::AbstractTrack for raw FLAC streams.
*/
/*!
* \brief Constructs a new track for the specified \a mediaFileInfo at the specified \a startOffset.
*
* The stream of the \a mediaFileInfo instance is used as input stream.
*/
FlacStream::FlacStream(MediaFileInfo &mediaFileInfo, uint64 startOffset) :
AbstractTrack(mediaFileInfo.stream(), startOffset),
m_mediaFileInfo(mediaFileInfo),
m_paddingSize(0),
m_streamOffset(0)
{
m_mediaType = MediaType::Audio;
}
/*!
* \brief Creates a new Vorbis comment for the stream.
* \remarks Just returns the current Vorbis comment if already present.
*/
VorbisComment *FlacStream::createVorbisComment()
{
if(!m_vorbisComment) {
m_vorbisComment = make_unique<VorbisComment>();
}
return m_vorbisComment.get();
}
void FlacStream::internalParseHeader()
{
static const string context("parsing raw FLAC header");
if(!m_istream) {
throw NoDataFoundException();
}
m_istream->seekg(m_startOffset, ios_base::beg);
char buffer[0x22];
// check signature
if(m_reader.readUInt32BE() == 0x664C6143) {
m_format = GeneralMediaFormat::Flac;
// parse meta data blocks
for(FlacMetaDataBlockHeader header; !header.isLast(); ) {
// parse block header
m_istream->read(buffer, 4);
header.parseHeader(buffer);
// remember start offset
const auto startOffset = m_istream->tellg();
// parse relevant meta data
switch(static_cast<FlacMetaDataBlockType>(header.type())) {
case FlacMetaDataBlockType::StreamInfo:
if(header.dataSize() >= 0x22) {
m_istream->read(buffer, 0x22);
FlacMetaDataBlockStreamInfo streamInfo;
streamInfo.parse(buffer);
m_channelCount = streamInfo.channelCount();
m_samplingFrequency = streamInfo.samplingFrequency();
m_sampleCount = streamInfo.totalSampleCount();
m_bitsPerSample = streamInfo.bitsPerSample();
} else {
addNotification(NotificationType::Critical, "\"METADATA_BLOCK_STREAMINFO\" is truncated and will be ignored.", context);
}
break;
case FlacMetaDataBlockType::VorbisComment:
// parse Vorbis comment
// if more then one comment exist, simply thread those comments as one
if(!m_vorbisComment) {
m_vorbisComment = make_unique<VorbisComment>();
}
try {
m_vorbisComment->parse(*m_istream, header.dataSize(), VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte);
} catch(const Failure &) {
// error is logged via notifications, just continue with the next metadata block
}
break;
case FlacMetaDataBlockType::Picture:
try {
// parse the cover
VorbisCommentField coverField;
coverField.setId(m_vorbisComment->fieldId(KnownField::Cover));
FlacMetaDataBlockPicture picture(coverField.value());
picture.parse(*m_istream, header.dataSize());
coverField.setTypeInfo(picture.pictureType());
// add the cover to the Vorbis comment
if(!m_vorbisComment) {
// create one if none exists yet
m_vorbisComment = make_unique<VorbisComment>();
m_vorbisComment->setVendor(TagValue(APP_NAME " v" APP_VERSION, TagTextEncoding::Utf8));
}
m_vorbisComment->fields().insert(make_pair(coverField.id(), move(coverField)));
} catch(const TruncatedDataException &) {
addNotification(NotificationType::Critical, "\"METADATA_BLOCK_PICTURE\" is truncated and will be ignored.", context);
}
break;
case FlacMetaDataBlockType::Padding:
m_paddingSize += 4 + header.dataSize();
break;
default:
;
}
// seek to next block
m_istream->seekg(startOffset + static_cast<decltype(startOffset)>(header.dataSize()));
// TODO: check first FLAC frame
}
m_streamOffset = m_istream->tellg();
} else {
addNotification(NotificationType::Critical, "Signature (fLaC) not found.", context);
throw InvalidDataException();
}
}
/*!
* \brief Writes the FLAC metadata header to the specified \a outputStream.
*
* This basically copies all "METADATA_BLOCK_HEADER" of the current stream to the specified \a outputStream, except:
*
* - Vorbis comment is updated.
* - "METADATA_BLOCK_PICTURE" are updated.
* - Padding is skipped
*
* \returns Returns the start offset of the last "METADATA_BLOCK_HEADER" withing \a outputStream.
*/
uint32 FlacStream::makeHeader(ostream &outputStream)
{
istream &originalStream = m_mediaFileInfo.stream();
originalStream.seekg(m_startOffset + 4);
CopyHelper<512> copy;
// write signature
BE::getBytes(static_cast<uint32>(0x664C6143u), copy.buffer());
outputStream.write(copy.buffer(), 4);
uint32 lastStartOffset = 0;
// write meta data blocks which don't need to be adjusted
for(FlacMetaDataBlockHeader header; !header.isLast(); ) {
// parse block header
m_istream->read(copy.buffer(), 4);
header.parseHeader(copy.buffer());
// parse relevant meta data
switch(static_cast<FlacMetaDataBlockType>(header.type())) {
case FlacMetaDataBlockType::VorbisComment:
case FlacMetaDataBlockType::Picture:
case FlacMetaDataBlockType::Padding:
m_istream->seekg(header.dataSize(), ios_base::cur);
break; // written separately/ignored
default:
m_istream->seekg(-4, ios_base::cur);
lastStartOffset = outputStream.tellp();
copy.copy(originalStream, outputStream, 4 + header.dataSize());
}
}
// write Vorbis comment
if(m_vorbisComment) {
// leave 4 bytes space for the "METADATA_BLOCK_HEADER"
lastStartOffset = outputStream.tellp();
outputStream.write(copy.buffer(), 4);
// determine cover ID since covers must be written separately
const auto coverId = m_vorbisComment->fieldId(KnownField::Cover);
// write Vorbis comment
m_vorbisComment->make(outputStream, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte | VorbisCommentFlags::NoCovers);
// write "METADATA_BLOCK_HEADER"
const uint32 endOffset = outputStream.tellp();
FlacMetaDataBlockHeader header;
header.setType(FlacMetaDataBlockType::VorbisComment);
header.setDataSize(endOffset - lastStartOffset - 4);
header.setLast(!m_vorbisComment->hasField(coverId));
outputStream.seekp(lastStartOffset);
header.makeHeader(outputStream);
outputStream.seekp(endOffset);
// write cover fields separately as "METADATA_BLOCK_PICTURE"
if(!header.isLast()) {
header.setType(FlacMetaDataBlockType::Picture);
const auto coverFields = m_vorbisComment->fields().equal_range(coverId);
for(auto i = coverFields.first; i != coverFields.second; ) {
lastStartOffset = outputStream.tellp();
FlacMetaDataBlockPicture pictureBlock(i->second.value());
pictureBlock.setPictureType(i->second.typeInfo());
header.setDataSize(pictureBlock.requiredSize());
header.setLast(++i == coverFields.second);
header.makeHeader(outputStream);
pictureBlock.make(outputStream);
}
}
}
return lastStartOffset;
}
/*!
* \brief Writes padding of the specified \a size to the specified \a stream.
* \remarks Size must be at least 4 bytes.
*/
void FlacStream::makePadding(ostream &stream, uint32 size, bool isLast)
{
// make header
FlacMetaDataBlockHeader header;
header.setType(FlacMetaDataBlockType::Padding);
header.setLast(isLast);
header.setDataSize(size -= 4);
header.makeHeader(stream);
// write zeroes
for(; size; --size) {
stream.put(0);
}
}
}

75
flac/flacstream.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef FLACSTREAM_H
#define FLACSTREAM_H
#include "../abstracttrack.h"
#include <c++utilities/misc/memory.h>
#include <iosfwd>
namespace Media {
class MediaFileInfo;
class VorbisComment;
class LIB_EXPORT FlacStream : public AbstractTrack
{
public:
FlacStream(MediaFileInfo &mediaFileInfo, uint64 startOffset);
~FlacStream();
TrackType type() const;
VorbisComment *vorbisComment() const;
VorbisComment *createVorbisComment();
uint32 paddingSize() const;
uint32 streamOffset() const;
uint32 makeHeader(std::ostream &stream);
static void makePadding(std::ostream &stream, uint32 size, bool isLast);
protected:
void internalParseHeader();
private:
MediaFileInfo &m_mediaFileInfo;
std::unique_ptr<VorbisComment> m_vorbisComment;
uint32 m_paddingSize;
uint32 m_streamOffset;
};
inline FlacStream::~FlacStream()
{}
inline TrackType FlacStream::type() const
{
return TrackType::FlacStream;
}
/*!
* \brief Returns the Vorbis comment if one is present in the stream.
*/
inline VorbisComment *FlacStream::vorbisComment() const
{
return m_vorbisComment.get();
}
/*!
* \brief Returns the padding size.
*/
inline uint32 FlacStream::paddingSize() const
{
return m_paddingSize;
}
/*!
* \brief Returns the start offset of the actual FLAC frames.
* \remarks This equals the size of the metadata header blocks.
*/
inline uint32 FlacStream::streamOffset() const
{
return m_streamOffset;
}
}
#endif // FLACSTREAM_H

View File

@ -12,8 +12,7 @@
#include <c++utilities/conversion/stringconversion.h>
#include <string>
#include <istream>
#include <ostream>
#include <iosfwd>
#include <vector>
namespace Media

View File

@ -9,7 +9,6 @@
#include "../exceptions.h"
#include "../backuphelper.h"
// include configuration from separate header file when building with CMake
#include "resources/config.h"
#include <c++utilities/conversion/stringconversion.h>

View File

@ -27,6 +27,9 @@
#include "./ogg/oggcontainer.h"
#include "./flac/flacstream.h"
#include "./flac/flacmetadata.h"
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/chrono/timespan.h>
#include <c++utilities/misc/memory.h>
@ -306,19 +309,31 @@ void MediaFileInfo::parseTracks()
m_container->parseTracks();
} else {
switch(m_containerFormat) {
case ContainerFormat::RiffWave:
m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
case ContainerFormat::Adts:
m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
break;
case ContainerFormat::Flac:
m_singleTrack = make_unique<FlacStream>(*this, m_containerOffset);
break;
case ContainerFormat::MpegAudioFrames:
m_singleTrack = make_unique<MpegAudioFrameStream>(stream(), m_containerOffset);
break;
case ContainerFormat::Adts:
m_singleTrack = make_unique<AdtsStream>(stream(), m_containerOffset);
case ContainerFormat::RiffWave:
m_singleTrack = make_unique<WaveAudioStream>(stream(), m_containerOffset);
break;
default:
throw NotImplementedException();
}
m_singleTrack->parseHeader();
switch(m_containerFormat) {
case ContainerFormat::Flac:
// FLAC streams might container padding
m_paddingSize += static_cast<FlacStream *>(m_singleTrack.get())->paddingSize();
break;
default:
;
}
}
m_tracksParsingStatus = ParsingStatus::Ok;
} catch(const NotImplementedException &) {
@ -539,27 +554,34 @@ bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagU
m_container->createTag();
}
} else {
// no container object present; creation of ID3 tag is possible
if(!hasAnyTag() && !treatUnknownFilesAsMp3Files) {
switch(containerFormat()) {
case ContainerFormat::MpegAudioFrames:
case ContainerFormat::Adts:
break;
default:
return false;
}
}
// create ID3 tags according to id3v2usage and id3v2usage
if(id3v1usage == TagUsage::Always) {
// always create ID3v1 tag -> ensure there is one
createId3v1Tag();
}
if(id3v2usage == TagUsage::Always) {
// always create ID3v2 tag -> ensure there is one and set version
if(!hasId3v2Tag()) {
createId3v2Tag()->setVersion(id3v2version, 0);
// no container object present
if(m_containerFormat == ContainerFormat::Flac) {
// creation of Vorbis comment is possible
static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
} else {
// creation of ID3 tag is possible
if(!hasAnyTag() && !treatUnknownFilesAsMp3Files) {
switch(containerFormat()) {
case ContainerFormat::MpegAudioFrames:
case ContainerFormat::Adts:
break;
default:
return false;
}
}
// create ID3 tags according to id3v2usage and id3v2usage
if(id3v1usage == TagUsage::Always) {
// always create ID3v1 tag -> ensure there is one
createId3v1Tag();
}
if(id3v2usage == TagUsage::Always) {
// always create ID3v2 tag -> ensure there is one and set version
if(!hasId3v2Tag()) {
createId3v2Tag()->setVersion(id3v2version, 0);
}
}
}
if(mergeMultipleSuccessiveId3v2Tags) {
mergeId3v2Tags();
}
@ -1031,12 +1053,13 @@ bool MediaFileInfo::areTracksSupported() const
bool MediaFileInfo::areTagsSupported() const
{
switch(m_containerFormat) {
case ContainerFormat::Mp4:
case ContainerFormat::MpegAudioFrames:
case ContainerFormat::Ogg:
case ContainerFormat::Matroska:
case ContainerFormat::Webm:
case ContainerFormat::Adts:
case ContainerFormat::Flac:
case ContainerFormat::Matroska:
case ContainerFormat::MpegAudioFrames:
case ContainerFormat::Mp4:
case ContainerFormat::Ogg:
case ContainerFormat::Webm:
// these container formats are supported
return true;
default:
@ -1081,7 +1104,11 @@ const vector<unique_ptr<MatroskaTag> > &MediaFileInfo::matroskaTags() const
*/
VorbisComment *MediaFileInfo::vorbisComment() const
{
return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount() > 0 ? static_cast<OggContainer *>(m_container.get())->tags().front().get() : nullptr;
return m_containerFormat == ContainerFormat::Ogg && m_container && m_container->tagCount()
? static_cast<OggContainer *>(m_container.get())->tags().front().get()
: (m_containerFormat == ContainerFormat::Flac && m_singleTrack
? static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()
: nullptr);
}
/*!
@ -1330,11 +1357,17 @@ bool MediaFileInfo::id3v2ToId3v1()
*/
void MediaFileInfo::tags(vector<Tag *> &tags) const
{
if(hasId3v1Tag())
if(hasId3v1Tag()) {
tags.push_back(m_id3v1Tag.get());
}
for(const unique_ptr<Id3v2Tag> &tag : m_id3v2Tags) {
tags.push_back(tag.get());
}
if(m_containerFormat == ContainerFormat::Flac && m_singleTrack) {
if(auto *vorbisComment = static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment()) {
tags.push_back(vorbisComment);
}
}
if(m_container) {
for(size_t i = 0, count = m_container->tagCount(); i < count; ++i) {
tags.push_back(m_container->tag(i));
@ -1342,6 +1375,17 @@ void MediaFileInfo::tags(vector<Tag *> &tags) const
}
}
/*!
* \brief Returns an indication whether a tag of any format is assigned.
*/
bool MediaFileInfo::hasAnyTag() const
{
return hasId3v1Tag()
|| hasId3v2Tag()
|| (m_container && m_container->tagCount())
|| (m_containerFormat == ContainerFormat::Flac && static_cast<FlacStream *>(m_singleTrack.get())->vorbisComment());
}
/*!
* \brief Returns all tags assigned to the current file.
*
@ -1371,9 +1415,9 @@ void MediaFileInfo::invalidated()
*/
void MediaFileInfo::makeMp3File()
{
const string context("making MP3 file");
static const string context("making MP3 file");
// there's no need to rewrite the complete file if there are no ID3v2 tags present or to be written
if(!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty()) {
if(!isForcingRewrite() && m_id3v2Tags.empty() && m_actualId3v2TagOffsets.empty() && m_saveFilePath.empty() && m_containerFormat != ContainerFormat::Flac) {
if(m_actualExistingId3v1Tag) {
// there is currently an ID3v1 tag at the end of the file
if(m_id3v1Tag) {
@ -1434,30 +1478,57 @@ void MediaFileInfo::makeMp3File()
addNotifications(*tag);
}
// check whether it is a raw FLAC stream
FlacStream *flacStream = (m_containerFormat == ContainerFormat::Flac ? static_cast<FlacStream *>(m_singleTrack.get()) : nullptr);
uint32 streamOffset; // where the actual stream starts
stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
uint32 startOfLastMetaDataBlock;
if(flacStream) {
// if it is a raw FLAC stream, make FLAC metadata
startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData);
tagsSize += flacMetaData.tellp();
streamOffset = flacStream->streamOffset();
} else {
// make no further metadata, just use the container offset as stream offset
streamOffset = static_cast<uint32>(m_containerOffset);
}
// check whether rewrite is required
bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > static_cast<uint32>(m_containerOffset));
bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
uint32 padding = 0;
if(!rewriteRequired) {
// rewriting is not forced and new tag is not too big for available space
// -> calculate new padding
padding = static_cast<uint32>(m_containerOffset) - tagsSize;
padding = streamOffset - tagsSize;
// -> check whether the new padding matches specifications
if(padding < minPadding() || padding > maxPadding()) {
rewriteRequired = true;
}
}
if(makers.empty()) {
// an ID3v2 tag shouldn't be written
if(makers.empty() && !flacStream) {
// an ID3v2 tag is not written and it is not a FLAC stream
// -> can't include padding
if(padding) {
// but padding would be present -> need to rewrite
padding = 0;
padding = 0; // can't write the preferred padding despite rewriting
rewriteRequired = true;
}
} else if(rewriteRequired) {
// rewriting is forced or new ID3v2 tag is too big for available space
// -> use preferred padding when rewriting anyways
padding = preferredPadding();
} else if(makers.empty() && flacStream && padding && padding < 4) {
// no ID3v2 tag -> must include padding in FLAC stream
// but padding of 1, 2, and 3 byte isn't possible -> need to rewrite
padding = preferredPadding();
rewriteRequired = true;
}
if(rewriteRequired && flacStream && makers.empty() && padding) {
// the first 4 byte of FLAC padding actually don't count because these
// can not be used for additional meta data
padding += 4;
}
updateStatus(rewriteRequired ? "Preparing streams for rewriting ..." : "Preparing streams for updating ...");
@ -1490,7 +1561,6 @@ void MediaFileInfo::makeMp3File()
}
}
} else { // !rewriteRequired
// reopen original file to ensure it is opened for writing
try {
@ -1511,30 +1581,56 @@ void MediaFileInfo::makeMp3File()
i->make(outputStream, 0);
}
// include padding into the last ID3v2 tag
makers.back().make(outputStream, padding);
} else {
// just write padding
makers.back().make(outputStream, (flacStream && padding && padding < 4) ? 0 : padding);
}
if(flacStream) {
if(padding && startOfLastMetaDataBlock) {
// if appending padding, ensure the last flag of the last "METADATA_BLOCK_HEADER" is not set
flacMetaData.seekg(startOfLastMetaDataBlock);
flacMetaData.seekp(startOfLastMetaDataBlock);
flacMetaData.put(static_cast<byte>(flacMetaData.peek()) & (0x80u - 1));
flacMetaData.seekg(0);
}
// write FLAC metadata
outputStream << flacMetaData.rdbuf();
// write padding
if(padding) {
flacStream->makePadding(outputStream, padding, true);
}
}
if(makers.empty() && !flacStream){
// just write padding (however, padding should be set to 0 in this case?)
for(; padding; --padding) {
outputStream.put(0);
}
}
// copy / skip media data
// copy / skip actual stream data
// -> determine media data size
uint64 mediaDataSize = size() - m_containerOffset;
uint64 mediaDataSize = size() - streamOffset;
if(m_actualExistingId3v1Tag) {
mediaDataSize -= 128;
}
if(rewriteRequired) {
// copy data from original file
updateStatus("Writing MPEG audio frames ...");
backupStream.seekg(m_containerOffset);
switch(m_containerFormat) {
case ContainerFormat::MpegAudioFrames:
updateStatus("Writing MPEG audio frames ...");
break;
default:
updateStatus("Writing frames ...");
}
backupStream.seekg(streamOffset);
CopyHelper<0x4000> copyHelper;
copyHelper.callbackCopy(backupStream, stream(), mediaDataSize, bind(&StatusProvider::isAborted, this), bind(&StatusProvider::updatePercentage, this, _1));
updatePercentage(100.0);
} else {
// just skip media data
// just skip actual stream data
outputStream.seekp(mediaDataSize, ios_base::cur);
}

View File

@ -6,8 +6,6 @@
#include "./basicfileinfo.h"
#include "./abstractcontainer.h"
#include <string>
#include <fstream>
#include <vector>
#include <memory>
@ -24,8 +22,6 @@ class OggContainer;
class EbmlElement;
class MatroskaTag;
class AbstractTrack;
class WaveAudioStream;
class MpegAudioFrameStream;
class VorbisComment;
enum class MediaType;
@ -335,14 +331,6 @@ inline bool MediaFileInfo::hasId3v2Tag() const
return m_id3v2Tags.size();
}
/*!
* \brief Returns an indication whether a tag of any format is assigned.
*/
inline bool MediaFileInfo::hasAnyTag() const
{
return hasId3v1Tag() || hasId3v2Tag() || (m_container && m_container->tagCount());
}
/*!
* \brief Returns a pointer to the assigned ID3v1 tag or nullptr if none is assigned.
*

View File

@ -259,6 +259,7 @@ public:
operator bool() const;
MediaFormat &operator+=(const MediaFormat &other);
bool operator==(GeneralMediaFormat general) const;
bool operator!=(GeneralMediaFormat general) const;
GeneralMediaFormat general;
unsigned char sub;
@ -299,6 +300,14 @@ inline bool MediaFormat::operator==(GeneralMediaFormat general) const
return this->general == general;
}
/*!
* \brief Returns whether the media format is not the specified general media format.
*/
inline bool MediaFormat::operator!=(GeneralMediaFormat general) const
{
return this->general != general;
}
/*!
* \brief Returns whether the media format is known.
*/

View File

@ -235,11 +235,12 @@ void OggContainer::internalParseTags()
break;
case GeneralMediaFormat::Opus:
// skip header (has already been detected by OggStream)
m_iterator.seekForward(8);
m_iterator.ignore(8);
comment->parse(m_iterator, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte);
break;
case GeneralMediaFormat::Flac:
comment->parse(m_iterator, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, 4);
m_iterator.ignore(4);
comment->parse(m_iterator, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte);
break;
default:
addNotification(NotificationType::Critical, "Stream format not supported.", "parsing tags from OGG streams");
@ -287,7 +288,7 @@ void OggContainer::internalParseTracks()
*/
void OggContainer::makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> &copyHelper, vector<uint32> &newSegmentSizes, VorbisComment *comment, OggParameter *params)
{
auto offset = buffer.tellp();
const auto offset = buffer.tellp();
switch(params->streamFormat) {
case GeneralMediaFormat::Vorbis:
comment->make(buffer);

View File

@ -123,6 +123,7 @@ void OggIterator::previousSegment()
* - Page headers are skipped (this is the whole purpose of this method).
* \throws Throws a TruncatedDataException if the end of the stream is reached before \a count bytes
* have been read.
* \sa readAll()
* \sa currentCharacterOffset()
* \sa seekForward()
*/
@ -149,6 +150,36 @@ void OggIterator::read(char *buffer, size_t count)
}
}
/*!
* \brief Reads all bytes from the OGG stream and writes it to the specified \a buffer.
* \remarks
* - Might increase the current page index and/or the current segment index.
* - Page headers are skipped (this is the whole purpose of this method).
* - Does not read more than \a max bytes from the buffer.
* \sa read()
* \sa currentCharacterOffset()
* \sa seekForward()
*/
size_t OggIterator::readAll(char *buffer, size_t max)
{
size_t bytesRead = 0;
while(*this && max) {
uint32 available = currentSegmentSize() - m_bytesRead;
stream().seekg(currentCharacterOffset());
if(max <= available) {
stream().read(buffer + bytesRead, max);
m_bytesRead += max;
return bytesRead + max;
} else {
stream().read(buffer + bytesRead, available);
nextSegment();
bytesRead += available;
max -= available;
}
}
return bytesRead;
}
/*!
* \brief Advances the position of the next character to be read from the OGG stream by \a count bytes.
* \remarks
@ -159,7 +190,7 @@ void OggIterator::read(char *buffer, size_t count)
* \sa currentCharacterOffset()
* \sa read()
*/
void OggIterator::seekForward(size_t count)
void OggIterator::ignore(size_t count)
{
uint32 available = currentSegmentSize() - m_bytesRead;
while(*this) {

View File

@ -3,7 +3,7 @@
#include "./oggpage.h"
#include <istream>
#include <iosfwd>
#include <vector>
namespace Media {
@ -31,13 +31,15 @@ public:
std::vector<uint32>::size_type currentSegmentIndex() const;
uint64 currentSegmentOffset() const;
uint64 currentCharacterOffset() const;
uint64 tellg() const;
uint32 currentSegmentSize() const;
void setFilter(uint32 streamSerialId);
void removeFilter();
bool areAllPagesFetched() const;
void read(char *buffer, size_t count);
void seekForward(size_t count);
bool bytesRemaining(size_t atLeast) const;
void read(char *buffer, std::size_t count);
size_t readAll(char *buffer, std::size_t max);
void ignore(std::size_t count = 1);
bool bytesRemaining(std::size_t atLeast) const;
operator bool() const;
OggIterator &operator++();
@ -201,6 +203,14 @@ inline uint64 OggIterator::currentCharacterOffset() const
return m_offset + m_bytesRead;
}
/*!
* \brief Same as currentCharacterOffset(); only provided for compliance with std::istream.
*/
inline uint64 OggIterator::tellg() const
{
return currentCharacterOffset();
}
/*!
* \brief Returns the size of the current segment.
*

View File

@ -6,7 +6,7 @@
#include <vector>
#include <numeric>
#include <istream>
#include <iosfwd>
namespace Media {

View File

@ -42,6 +42,7 @@ enum Sig48 : uint64
enum Sig32 : uint32
{
Elf = 0x7F454C46u,
Flac = 0x664C6143u,
JavaClassFile = 0xCAFEBABEu,
Ebml = 0x1A45DFA3u,
Mp4 = 0x66747970u,
@ -150,6 +151,8 @@ ContainerFormat parseSignature(const char *buffer, int bufferSize)
switch(sig >> 32) { // check 32-bit signatures
case Elf:
return ContainerFormat::Elf;
case Flac:
return ContainerFormat::Flac;
case JavaClassFile:
return ContainerFormat::JavaClassFile;
case Ebml:
@ -235,6 +238,7 @@ const char *containerFormatAbbreviation(ContainerFormat containerFormat, MediaTy
case ContainerFormat::Ar: return "a";
case ContainerFormat::Asf: return "asf";
case ContainerFormat::Elf: return "elf";
case ContainerFormat::Flac: return "flac";
case ContainerFormat::FlashVideo: return "flv";
case ContainerFormat::Gif87a:
case ContainerFormat::Gif89a: return "gif";
@ -315,6 +319,8 @@ const char *containerFormatName(ContainerFormat containerFormat)
return "Advanced Systems Format";
case ContainerFormat::Elf:
return "Executable and Linkable Format";
case ContainerFormat::Flac:
return "raw Free Lossless Audio Codec frames";
case ContainerFormat::FlashVideo:
return "Flash Video";
case ContainerFormat::Gif87a:
@ -417,6 +423,8 @@ const char *containerMimeType(ContainerFormat containerFormat, MediaType mediaTy
switch(containerFormat) {
case ContainerFormat::Asf:
return "video/x-ms-asf";
case ContainerFormat::Flac:
return "audio/flac";
case ContainerFormat::FlashVideo:
return "video/x-flv";
case ContainerFormat::Gif87a:

View File

@ -10,6 +10,8 @@ namespace Media {
/*!
* \brief Specifies the container format.
*
* Raw streams like ADTS or raw FLAC count as container format in this context.
*/
enum class ContainerFormat
{
@ -19,6 +21,7 @@ enum class ContainerFormat
Asf, /**< Advanced Systems Format */
Bzip2, /** bzip2 compressed file */
Elf, /**< Executable and Linkable Format */
Flac, /** < Free Lossless Audio Codec (raw stream) */
FlashVideo, /**< Flash (FLV) */
Gif87a, /**< Graphics Interchange Format (1987) */
Gif89a, /**< Graphics Interchange Format (1989) */

View File

@ -7,7 +7,7 @@
#include <c++utilities/chrono/timespan.h>
#include <c++utilities/chrono/datetime.h>
#include <istream>
#include <iosfwd>
#include <string>
#include <memory>

View File

@ -100,51 +100,51 @@ KnownField VorbisComment::knownField(const string &id) const
}
/*!
* \brief Parses tag information using the specified OGG \a iterator.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
* \brief Internal implementation for parsing.
*/
void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, size_t offset)
template<class StreamType>
void VorbisComment::internalParse(StreamType &stream, uint64 maxSize, VorbisCommentFlags flags)
{
// prepare parsing
invalidateStatus();
static const string context("parsing Vorbis comment");
auto startOffset = iterator.currentSegmentOffset() + offset;
iterator.seekForward(offset);
uint64 startOffset = static_cast<uint64>(stream.tellg());
try {
// read signature: 0x3 + "vorbis"
char sig[8];
bool skipSignature = flags & VorbisCommentFlags::NoSignature;
if(!skipSignature) {
iterator.read(sig, 7);
CHECK_MAX_SIZE(7);
stream.read(sig, 7);
skipSignature = (ConversionUtilities::BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
}
if(skipSignature) {
// read vendor (length prefixed string)
{
iterator.read(sig, 4);
CHECK_MAX_SIZE(4);
stream.read(sig, 4);
const auto vendorSize = LE::toUInt32(sig);
if(iterator.currentCharacterOffset() + vendorSize <= iterator.streamSize()) {
if(vendorSize <= maxSize) {
auto buff = make_unique<char []>(vendorSize);
iterator.read(buff.get(), vendorSize);
stream.read(buff.get(), vendorSize);
m_vendor.assignData(move(buff), vendorSize, TagDataType::Text, TagTextEncoding::Utf8);
// TODO: Is the vendor string actually UTF-8 (like the field values)?
} else {
addNotification(NotificationType::Critical, "Vendor information is truncated.", context);
throw TruncatedDataException();
}
maxSize -= vendorSize;
}
// read field count
iterator.read(sig, 4);
CHECK_MAX_SIZE(4);
stream.read(sig, 4);
uint32 fieldCount = LE::toUInt32(sig);
VorbisCommentField field;
const string &fieldId = field.id();
for(uint32 i = 0; i < fieldCount; ++i) {
// read fields
try {
field.parse(iterator);
field.parse(stream, maxSize);
fields().insert(pair<fieldType::identifierType, fieldType>(fieldId, field));
} catch(const TruncatedDataException &) {
addNotifications(field);
@ -156,20 +156,44 @@ void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, size_
field.invalidateNotifications();
}
if(!(flags & VorbisCommentFlags::NoFramingByte)) {
iterator.seekForward(1); // skip framing byte
stream.ignore(); // skip framing byte
}
m_size = static_cast<uint32>(static_cast<uint64>(iterator.currentCharacterOffset()) - startOffset);
m_size = static_cast<uint32>(static_cast<uint64>(stream.tellg()) - startOffset);
} else {
addNotification(NotificationType::Critical, "Signature is invalid.", context);
throw InvalidDataException();
}
} catch(const TruncatedDataException &) {
m_size = static_cast<uint32>(static_cast<uint64>(iterator.currentCharacterOffset()) - startOffset);
m_size = static_cast<uint32>(static_cast<uint64>(stream.tellg()) - startOffset);
addNotification(NotificationType::Critical, "Vorbis comment is truncated.", context);
throw;
}
}
/*!
* \brief Parses tag information using the specified OGG \a iterator.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
*/
void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags)
{
internalParse(iterator, iterator.streamSize(), flags);
}
/*!
* \brief Parses tag information using the specified OGG \a iterator.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
*/
void VorbisComment::parse(istream &stream, uint64 maxSize, VorbisCommentFlags flags)
{
internalParse(stream, maxSize, flags);
}
/*!
* \brief Writes tag information to the specified \a stream.
*
@ -197,14 +221,18 @@ void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags)
// write vendor
writer.writeUInt32LE(vendor.size());
writer.writeString(vendor);
// write field count
writer.writeUInt32LE(fieldCount());
// write field count later
const auto fieldCountOffset = stream.tellp();
writer.writeUInt32LE(0);
// write fields
uint32 fieldsWritten = 0;
for(auto i : fields()) {
VorbisCommentField &field = i.second;
if(!field.value().isEmpty()) {
try {
field.make(writer);
if(field.make(writer, flags)) {
++fieldsWritten;
}
} catch(const Failure &) {
// nothing to do here since notifications will be added anyways
}
@ -213,6 +241,11 @@ void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags)
field.invalidateNotifications();
}
}
// write field count
const auto framingByteOffset = stream.tellp();
stream.seekp(fieldCountOffset);
writer.writeUInt32LE(fieldsWritten);
stream.seekp(framingByteOffset);
// write framing byte
if(!(flags & VorbisCommentFlags::NoFramingByte)) {
stream.put(0x01);

View File

@ -12,26 +12,6 @@ namespace Media {
class OggIterator;
class VorbisComment;
/*!
* \brief The VorbisCommentFlags enum specifies flags which controls parsing and making of Vorbis comments.
*/
enum class VorbisCommentFlags : byte
{
None = 0x0, /**< Regular parsing/making. */
NoSignature = 0x1, /**< Skips the signature when parsing; does not make the signature when making. */
NoFramingByte = 0x2 /**< Doesn't expect the framing bit to be present when parsing; does not make the framing bit when making. */
};
inline bool operator &(VorbisCommentFlags lhs, VorbisCommentFlags rhs)
{
return static_cast<byte>(lhs) & static_cast<byte>(rhs);
}
inline VorbisCommentFlags operator |(VorbisCommentFlags lhs, VorbisCommentFlags rhs)
{
return static_cast<VorbisCommentFlags>(static_cast<byte>(lhs) | static_cast<byte>(rhs));
}
class LIB_EXPORT VorbisComment : public FieldMapBasedTag<VorbisCommentField, CaseInsensitiveStringComparer>
{
public:
@ -47,12 +27,17 @@ public:
std::string fieldId(KnownField field) const;
KnownField knownField(const std::string &id) const;
void parse(OggIterator &iterator, VorbisCommentFlags flags = VorbisCommentFlags::None, std::size_t offset = 0);
void parse(OggIterator &iterator, VorbisCommentFlags flags = VorbisCommentFlags::None);
void parse(std::istream &stream, uint64 maxSize, VorbisCommentFlags flags = VorbisCommentFlags::None);
void make(std::ostream &stream, VorbisCommentFlags flags = VorbisCommentFlags::None);
const TagValue &vendor() const;
void setVendor(const TagValue &vendor);
private:
template<class StreamType>
void internalParse(StreamType &stream, uint64 maxSize, VorbisCommentFlags flags);
private:
TagValue m_vendor;
};

View File

@ -42,25 +42,26 @@ VorbisCommentField::VorbisCommentField(const identifierType &id, const TagValue
{}
/*!
* \brief Parses a field from the stream read using the specified \a reader.
*
* The position of the current character in the input stream is expected to be
* at the beginning of the field to be parsed.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
* \brief Internal implementation for parsing.
*/
void VorbisCommentField::parse(OggIterator &iterator)
template<class StreamType>
void VorbisCommentField::internalParse(StreamType &stream, uint64 &maxSize)
{
static const string context("parsing Vorbis comment field");
char buff[4];
iterator.read(buff, 4);
if(auto size = LE::toUInt32(buff)) { // read size
if(iterator.currentCharacterOffset() + size <= iterator.streamSize()) {
if(maxSize < 4) {
addNotification(NotificationType::Critical, "Field expected.", context);
throw TruncatedDataException();
} else {
maxSize -= 4;
}
stream.read(buff, 4);
if(const auto size = LE::toUInt32(buff)) { // read size
if(size <= maxSize) {
maxSize -= size;
// read data
auto data = make_unique<char []>(size);
iterator.read(data.get(), size);
stream.read(data.get(), size);
uint32 idSize = 0;
for(const char *i = data.get(), *end = data.get() + size; i != end && *i != '='; ++i, ++idSize);
// extract id
@ -77,13 +78,16 @@ void VorbisCommentField::parse(OggIterator &iterator)
bufferStream.exceptions(ios_base::failbit | ios_base::badbit);
bufferStream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
FlacMetaDataBlockPicture pictureBlock(value());
pictureBlock.parse(bufferStream);
pictureBlock.parse(bufferStream, decoded.second);
setTypeInfo(pictureBlock.pictureType());
} catch (const ios_base::failure &) {
} catch(const ios_base::failure &) {
addNotification(NotificationType::Critical, "An IO error occured when reading the METADATA_BLOCK_PICTURE struct.", context);
throw Failure();
} catch (const ConversionException &) {
addNotification(NotificationType::Critical, "Base64 data from METADATA_BLOCK_PICTURE is invalid.", context);
} catch(const TruncatedDataException &) {
addNotification(NotificationType::Critical, "METADATA_BLOCK_PICTURE is truncated.", context);
throw;
} catch(const ConversionException &) {
addNotification(NotificationType::Critical, "Base64 coding of METADATA_BLOCK_PICTURE is invalid.", context);
throw InvalidDataException();
}
} else if(id().size() + 1 < size) {
@ -97,14 +101,62 @@ void VorbisCommentField::parse(OggIterator &iterator)
}
}
/*!
* \brief Parses a field using the specified \a iterator.
*
* The currentCharacterOffset() of the iterator is expected to be
* at the beginning of the field to be parsed.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
*/
void VorbisCommentField::parse(OggIterator &iterator)
{
uint64 maxSize = iterator.streamSize() - iterator.currentCharacterOffset();
internalParse(iterator, maxSize);
}
/*!
* \brief Parses a field using the specified \a iterator.
*
* The currentCharacterOffset() of the iterator is expected to be
* at the beginning of the field to be parsed.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
*/
void VorbisCommentField::parse(OggIterator &iterator, uint64 &maxSize)
{
internalParse(iterator, maxSize);
}
/*!
* \brief Parses a field from the specified \a stream.
*
* The position of the current character in the input stream is expected to be
* at the beginning of the field to be parsed.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
*/
void VorbisCommentField::parse(istream &stream, uint64 &maxSize)
{
internalParse(stream, maxSize);
}
/*!
* \brief Writes the field to a stream using the specified \a writer.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws Media::Failure or a derived exception when a making
* error occurs.
* \returns Returns whether the field has been written. (Some fields might be skipped
* when specific \a flags are set.)
*/
void VorbisCommentField::make(BinaryWriter &writer)
bool VorbisCommentField::make(BinaryWriter &writer, VorbisCommentFlags flags)
{
static const string context("making Vorbis comment field");
if(id().empty()) {
@ -114,6 +166,9 @@ void VorbisCommentField::make(BinaryWriter &writer)
// try to convert value to string
string valueString;
if(id() == VorbisCommentIds::cover()) {
if(flags & VorbisCommentFlags::NoCovers) {
return false;
}
// make cover
if(value().type() != TagDataType::Picture) {
addNotification(NotificationType::Critical, "Assigned value of cover field is not picture data.", context);
@ -143,10 +198,11 @@ void VorbisCommentField::make(BinaryWriter &writer)
writer.writeString(id());
writer.writeChar('=');
writer.writeString(valueString);
} catch(ConversionException &) {
} catch(const ConversionException &) {
addNotification(NotificationType::Critical, "Assigned value can not be converted appropriately.", context);
throw InvalidDataException();
}
return true;
}
}

View File

@ -11,6 +11,27 @@ class BinaryWriter;
namespace Media {
/*!
* \brief The VorbisCommentFlags enum specifies flags which controls parsing and making of Vorbis comments.
*/
enum class VorbisCommentFlags : byte
{
None = 0x0, /**< Regular parsing/making. */
NoSignature = 0x1, /**< Skips the signature when parsing and making. */
NoFramingByte = 0x2, /**< Doesn't expect the framing bit to be present when parsing; does not make the framing bit when making. */
NoCovers = 0x4 /**< Skips all covers when making. */
};
inline bool operator &(VorbisCommentFlags lhs, VorbisCommentFlags rhs)
{
return static_cast<byte>(lhs) & static_cast<byte>(rhs);
}
inline VorbisCommentFlags operator |(VorbisCommentFlags lhs, VorbisCommentFlags rhs)
{
return static_cast<VorbisCommentFlags>(static_cast<byte>(lhs) | static_cast<byte>(rhs));
}
class VorbisCommentField;
/*!
@ -45,12 +66,18 @@ public:
VorbisCommentField(const identifierType &id, const TagValue &value);
void parse(OggIterator &iterator);
void make(IoUtilities::BinaryWriter &writer);
void parse(OggIterator &iterator, uint64 &maxSize);
void parse(std::istream &stream, uint64 &maxSize);
bool make(IoUtilities::BinaryWriter &writer, VorbisCommentFlags flags = VorbisCommentFlags::None);
bool isAdditionalTypeInfoUsed() const;
bool supportsNestedFields() const;
protected:
void cleared();
private:
template<class StreamType>
void internalParse(StreamType &stream, uint64 &maxSize);
};
/*!

View File

@ -1,6 +1,8 @@
#ifndef VORBISPACKAGETYPES_H
#define VORBISPACKAGETYPES_H
#include <c++utilities/conversion/types.h>
namespace Media {
/*!