Support FLAC in Ogg

This commit is contained in:
Martchus 2016-05-14 00:24:01 +02:00
parent 8d8322948d
commit bbafd16dcc
23 changed files with 975 additions and 196 deletions

View File

@ -34,6 +34,8 @@ set(HEADER_FILES
ogg/oggpage.h
ogg/oggstream.h
opus/opusidentificationheader.h
flac/flactooggmappingheader.h
flac/flacmetadata.h
positioninset.h
signature.h
size.h
@ -105,6 +107,8 @@ set(SRC_FILES
ogg/oggpage.cpp
ogg/oggstream.cpp
opus/opusidentificationheader.cpp
flac/flactooggmappingheader.cpp
flac/flacmetadata.cpp
signature.cpp
statusprovider.cpp
tag.cpp

View File

@ -5,7 +5,9 @@ C++ library for reading and writing MP4 (iTunes), ID3, Vorbis and Matroska tags.
The tag library can read and write the following tag formats:
- iTunes-style MP4 tags (MP4-DASH is supported)
- ID3v1 and ID3v2 tags
- Vorbis and Opus comments (cover art via "METADATA_BLOCK_PICTURE" is supported) in Ogg streams
- conversion between ID3v1 and different versions of ID3v2
- Vorbis, Opus and FLAC comments in Ogg streams
- cover art via "METADATA_BLOCK_PICTURE" is supported
- Matroska/WebM tags and attachments
## File layout options

View File

@ -252,9 +252,10 @@ void AbstractContainer::internalMakeFile()
/*!
* \brief Creates and returns a tag for the specified \a target.
* \remarks
* - If an empty \a target is specified it will be ignored.
* - If there is already a tag (for the specified \a target) present,
* no new tag will be created. The present tag will be returned instead.
* - If an empty \a target is specified it will be ignored.
* - If targets aren't supported the specified \a target will be ignored.
* - If no tag could be created, nullptr is returned.
* - The container keeps the ownership over the created tag.
*/

127
flac/flacmetadata.cpp Normal file
View File

@ -0,0 +1,127 @@
#include "./flacmetadata.h"
#include "../exceptions.h"
#include "../tagvalue.h"
#include <c++utilities/conversion/binaryconversion.h>
#include <c++utilities/io/bitreader.h>
#include <c++utilities/io/binaryreader.h>
#include <c++utilities/io/binarywriter.h>
#include <c++utilities/misc/memory.h>
#include <cstring>
using namespace std;
using namespace ConversionUtilities;
using namespace IoUtilities;
namespace Media {
/*!
* \class Media::FlacMetaDataBlockHeader
* \brief The FlacMetaDataBlockHeader class is a FLAC "METADATA_BLOCK_HEADER" parser and maker.
* \sa https://xiph.org/flac/format.html
*/
/*!
* \brief Parses the FLAC "METADATA_BLOCK_HEADER" which is read using the specified \a iterator.
* \remarks The specified \a buffer must be at least 4 bytes long.
*/
void FlacMetaDataBlockHeader::parseHeader(const char *buffer)
{
m_last = *buffer & 0x80;
m_type = *buffer & (0x80 - 1);
m_dataSize = BE::toUInt24(buffer + 1);
}
/*!
* \brief Writes the header to the specified \a outputStream.
* \remarks Writes always 4 bytes.
*/
void FlacMetaDataBlockHeader::makeHeader(std::ostream &outputStream)
{
byte buff[4];
*buff = (m_last ? (0x80 | m_type) : m_type);
BE::getBytes24(m_dataSize, reinterpret_cast<char *>(buff) + 1);
outputStream.write(reinterpret_cast<char *>(buff), sizeof(buff));
}
/*!
* \class Media::FlacMetaDataBlockStreamInfo
* \brief The FlacMetaDataBlockStreamInfo class is a FLAC "METADATA_BLOCK_STREAMINFO" parser.
* \sa https://xiph.org/flac/format.html
*/
/*!
* \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.
*/
void FlacMetaDataBlockStreamInfo::parse(const char *buffer)
{
BitReader reader(buffer, 0x22);
m_minBlockSize = reader.readBits<uint16>(16);
m_maxBlockSize = reader.readBits<uint16>(16);
m_minFrameSize = reader.readBits<uint32>(24);
m_maxFrameSize = reader.readBits<uint32>(24);
m_samplingFrequency = reader.readBits<uint32>(20);
m_channelCount = reader.readBits<byte>(3) + 1;
m_bitsPerSample = reader.readBits<byte>(5) + 1;
m_totalSampleCount = reader.readBits<uint64>(36);
memcpy(m_md5Sum, buffer + 0x22 - sizeof(m_md5Sum), sizeof(m_md5Sum));
}
/*!
* \class Media::FlacMetaDataBlockPicture
* \brief The FlacMetaDataBlockPicture class is a FLAC "METADATA_BLOCK_PICTURE" parser and maker.
* \sa https://xiph.org/flac/format.html
*/
/*!
* \brief Parses the FLAC "METADATA_BLOCK_PICTURE".
*/
void FlacMetaDataBlockPicture::parse(istream &inputStream)
{
BinaryReader reader(&inputStream);
m_pictureType = reader.readUInt32BE();
auto size = reader.readUInt32BE();
m_value.setMimeType(reader.readString(size));
size = reader.readUInt32BE();
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();
auto data = make_unique<char[]>(size);
inputStream.read(data.get(), size);
m_value.assignData(move(data), size, TagDataType::Picture);
}
/*!
* \brief Returns the number of bytes make() will write.
* \remarks Any changes to the object will invalidate this value.
*/
uint32 FlacMetaDataBlockPicture::requiredSize() const
{
return 32 + m_value.mimeType().size() + m_value.description().size() + m_value.dataSize();
}
/*!
* \brief Makes the FLAC "METADATA_BLOCK_PICTURE".
*/
void FlacMetaDataBlockPicture::make(ostream &outputStream)
{
BinaryWriter writer(&outputStream);
writer.writeUInt32BE(pictureType());
writer.writeUInt32BE(m_value.mimeType().size());
writer.writeString(m_value.mimeType());
writer.writeUInt32BE(m_value.description().size());
writer.writeString(m_value.description());
writer.writeUInt32BE(0); // skip width
writer.writeUInt32BE(0); // skip height
writer.writeUInt32BE(0); // skip color depth
writer.writeUInt32BE(0); // skip number of colors used
writer.writeUInt32BE(m_value.dataSize());
writer.write(value().dataPointer(), m_value.dataSize());
}
}

312
flac/flacmetadata.h Normal file
View File

@ -0,0 +1,312 @@
#ifndef MEDIA_FLACMETADATAHEADER_H
#define MEDIA_FLACMETADATAHEADER_H
#include <c++utilities/application/global.h>
#include <c++utilities/conversion/types.h>
#include <iostream>
namespace Media {
class TagValue;
/*!
* \brief The FlacMetaDataBlockType enum specifies the type of FlacMetaDataBlockHeader.
*/
enum class FlacMetaDataBlockType : byte
{
StreamInfo = 0,
Padding,
Application,
SeekTable,
VorbisComment,
CuseSheet,
Picture
};
constexpr bool operator ==(byte lhs, FlacMetaDataBlockType type)
{
return lhs == static_cast<byte>(type);
}
constexpr bool operator !=(byte lhs, FlacMetaDataBlockType type)
{
return lhs != static_cast<byte>(type);
}
class LIB_EXPORT FlacMetaDataBlockHeader
{
public:
FlacMetaDataBlockHeader();
void parseHeader(const char *buffer);
void makeHeader(std::ostream &outputStream);
byte isLast() const;
void setLast(byte last);
byte type() const;
void setType(FlacMetaDataBlockType type);
uint32 dataSize() const;
void setDataSize(uint32 dataSize);
private:
byte m_last;
byte m_type;
uint32 m_dataSize;
};
/*!
* \brief Constructs a new FLAC "METADATA_BLOCK_HEADER".
*/
inline FlacMetaDataBlockHeader::FlacMetaDataBlockHeader() :
m_last(0),
m_type(0),
m_dataSize(0)
{}
/*!
* \brief Returns whether this is the last metadata block before the audio blocks.
*/
inline byte FlacMetaDataBlockHeader::isLast() const
{
return m_last;
}
/*!
* \brief Sets whether this is the last metadata block before the audio blocks.
*/
inline void FlacMetaDataBlockHeader::setLast(byte last)
{
m_last = last;
}
/*!
* \brief Returns the block type.
* \sa FlacMetaDataBlockType
*/
inline byte FlacMetaDataBlockHeader::type() const
{
return m_type;
}
/*!
* \brief Sets the block type.
*/
inline void FlacMetaDataBlockHeader::setType(FlacMetaDataBlockType type)
{
m_type = static_cast<byte>(type);
}
/*!
* \brief Returns the length in bytes of the meta data (excluding the size of the header itself).
*/
inline uint32 FlacMetaDataBlockHeader::dataSize() const
{
return m_dataSize;
}
/*!
* \brief Sets the length in bytes of the meta data (excluding the size of the header itself).
* \remarks Max value is (2^24 - 1).
*/
inline void FlacMetaDataBlockHeader::setDataSize(uint32 dataSize)
{
m_dataSize = dataSize;
}
class LIB_EXPORT FlacMetaDataBlockStreamInfo
{
public:
FlacMetaDataBlockStreamInfo();
void parse(const char *buffer);
uint16 minBlockSize() const;
uint16 maxBlockSize() const;
uint32 minFrameSize() const;
uint32 maxFrameSize() const;
uint32 samplingFrequency() const;
byte channelCount() const;
byte bitsPerSample() const;
uint64 totalSampleCount() const;
const char *md5Sum() const;
private:
uint16 m_minBlockSize;
uint16 m_maxBlockSize;
uint32 m_minFrameSize;
uint32 m_maxFrameSize;
uint32 m_samplingFrequency;
byte m_channelCount;
byte m_bitsPerSample;
uint64 m_totalSampleCount;
char m_md5Sum[16];
};
/*!
* \brief Constructs a new FLAC "METADATA_BLOCK_STREAMINFO".
*/
inline FlacMetaDataBlockStreamInfo::FlacMetaDataBlockStreamInfo() :
m_minBlockSize(0),
m_maxBlockSize(0),
m_minFrameSize(0),
m_maxFrameSize(0),
m_samplingFrequency(0),
m_channelCount(0),
m_bitsPerSample(0),
m_totalSampleCount(0),
m_md5Sum{0}
{}
/*!
* \brief Returns the minimum block size (in samples) used in the stream.
*/
inline uint16 FlacMetaDataBlockStreamInfo::minBlockSize() const
{
return m_minBlockSize;
}
/*!
* \brief Returns the maximum block size (in samples) used in the stream.
*
* (Minimum blocksize == maximum blocksize) implies a fixed-blocksize stream.
*/
inline uint16 FlacMetaDataBlockStreamInfo::maxBlockSize() const
{
return m_maxBlockSize;
}
/*!
* \brief Returns the minimum frame size (in bytes) used in the stream.
*
* May be 0 to imply the value is not known.
*/
inline uint32 FlacMetaDataBlockStreamInfo::minFrameSize() const
{
return m_minFrameSize;
}
/*!
* \brief The maximum frame size (in bytes) used in the stream.
*
* May be 0 to imply the value is not known.
*/
inline uint32 FlacMetaDataBlockStreamInfo::maxFrameSize() const
{
return m_maxFrameSize;
}
/*!
* \brief Returns the sampling frequency in Hz.
*
* Though 20 bits are available, the maximum sample rate is limited by the
* structure of frame headers to 655350Hz. Also, a value of 0 is invalid.
*/
inline uint32 FlacMetaDataBlockStreamInfo::samplingFrequency() const
{
return m_samplingFrequency;
}
/*!
* \brief Returns the number of channels.
*
* FLAC supports from 1 to 8 channels .
*/
inline byte FlacMetaDataBlockStreamInfo::channelCount() const
{
return m_channelCount;
}
/*!
* \brief Returns the bits per sample.
*
* FLAC supports from 4 to 32 bits per sample.
* Currently the reference encoder and decoders only support up
* to 24 bits per sample.
*/
inline byte FlacMetaDataBlockStreamInfo::bitsPerSample() const
{
return m_bitsPerSample;
}
/*!
* \brief Returns the total samples in stream.
*
* 'Samples' means inter-channel sample, i.e. one second of 44.1Khz audio
* will have 44100 samples regardless of the number of channels.
*
* A value of zero here means the number of total samples is unknown.
*/
inline uint64 FlacMetaDataBlockStreamInfo::totalSampleCount() const
{
return m_totalSampleCount;
}
/*!
* \brief Returns the MD5 signature of the unencoded audio data.
*
* This allows the decoder to determine if an error exists in the
* audio data even when the error does not result in an invalid bitstream.
*/
inline const char *FlacMetaDataBlockStreamInfo::md5Sum() const
{
return m_md5Sum;
}
class LIB_EXPORT FlacMetaDataBlockPicture
{
public:
FlacMetaDataBlockPicture(TagValue &tagValue);
void parse(std::istream &inputStream);
uint32 requiredSize() const;
void make(std::ostream &outputStream);
uint32 pictureType() const;
void setPictureType(uint32 pictureType);
TagValue &value();
private:
uint32 m_pictureType;
TagValue &m_value;
};
/*!
* \brief Constructs a new FLAC "METADATA_BLOCK_PICTURE".
*
* The picture is read from/stored to the specified \a tagValue.
* The FlacMetaDataBlockPicture does not take ownership over
* the specified \a tagValue.
*/
inline FlacMetaDataBlockPicture::FlacMetaDataBlockPicture(TagValue &tagValue) :
m_pictureType(0),
m_value(tagValue)
{}
/*!
* \brief Returns the picture type according to the ID3v2 APIC frame.
*/
inline uint32 FlacMetaDataBlockPicture::pictureType() const
{
return m_pictureType;
}
/*!
* \brief Sets the picture type according to the ID3v2 APIC frame.
*/
inline void FlacMetaDataBlockPicture::setPictureType(uint32 pictureType)
{
m_pictureType = pictureType;
}
/*!
* \brief Returns the tag value the picture is read from/stored to.
*/
inline TagValue &FlacMetaDataBlockPicture::value()
{
return m_value;
}
}
#endif // MEDIA_FLACMETADATAHEADER_H

View File

@ -0,0 +1,56 @@
#include "./flactooggmappingheader.h"
#include "../ogg/oggiterator.h"
#include "../exceptions.h"
#include <c++utilities/conversion/binaryconversion.h>
using namespace std;
using namespace ConversionUtilities;
namespace Media {
/*!
* \class Media::FlacToOggMappingHeader
* \brief The FlacToOggMappingHeader class is a FLAC-to-Ogg mapping header parser.
* \sa https://xiph.org/flac/ogg_mapping.html
*/
/*!
* \brief Parses the FLAC-to-Ogg mapping header which is read using the specified \a iterator.
* \remarks The header is assumed to start at the current position of \a iterator.
*/
void FlacToOggMappingHeader::parseHeader(OggIterator &iterator)
{
// prepare parsing
char buff[0x0D + 0x04 + 0x22 - 0x05];
iterator.read(buff, 5);
if(*buff != 0x7Fu || BE::toUInt32(buff + 1) != 0x464C4143u) {
throw InvalidDataException(); // not FLAC-to-Ogg mapping header
}
iterator.read(buff, sizeof(buff));
// parse FLAC-to-Ogg mapping header
m_majorVersion = static_cast<byte>(*(buff + 0x00));
m_minorVersion = static_cast<byte>(*(buff + 0x01));
m_headerCount = BE::toUInt16(buff + 0x02);
if(BE::toUInt32(buff + 0x04) != 0x664C6143u) {
throw InvalidDataException(); // native FLAC signature not present
}
// parse "METADATA_BLOCK_HEADER"
FlacMetaDataBlockHeader header;
header.parseHeader(buff + 0x0D - 0x05);
if(header.type() != FlacMetaDataBlockType::StreamInfo) {
throw InvalidDataException(); // "METADATA_BLOCK_STREAMINFO" expected
}
if(header.dataSize() < 0x22) {
throw TruncatedDataException(); // "METADATA_BLOCK_STREAMINFO" is truncated
}
// parse "METADATA_BLOCK_STREAMINFO"
m_streamInfo.parse(buff + 0x0D + 0x04 - 0x05);
}
}

View File

@ -0,0 +1,72 @@
#ifndef MEDIA_FLACIDENTIFICATIONHEADER_H
#define MEDIA_FLACIDENTIFICATIONHEADER_H
#include "./flacmetadata.h"
namespace Media {
class OggIterator;
class LIB_EXPORT FlacToOggMappingHeader
{
public:
FlacToOggMappingHeader();
void parseHeader(OggIterator &iterator);
byte majorVersion() const;
byte minorVersion() const;
uint16 headerCount() const;
const FlacMetaDataBlockStreamInfo &streamInfo() const;
private:
byte m_majorVersion;
byte m_minorVersion;
uint16 m_headerCount;
FlacMetaDataBlockStreamInfo m_streamInfo;
};
/*!
* \brief Constructs a new FLAC identification header.
*/
inline FlacToOggMappingHeader::FlacToOggMappingHeader() :
m_majorVersion(0),
m_minorVersion(0),
m_headerCount(0)
{}
/*!
* \brief Returns the major version for the mapping (which should be 1 currently).
*/
inline byte FlacToOggMappingHeader::majorVersion() const
{
return m_majorVersion;
}
/*!
* \brief Returns the version for the mapping (which should be 0 currently).
*/
inline byte FlacToOggMappingHeader::minorVersion() const
{
return m_minorVersion;
}
/*!
* \brief Returns the number of header (non-audio) packets, not including this one.
*/
inline uint16 FlacToOggMappingHeader::headerCount() const
{
return m_headerCount;
}
/*!
* \brief Returns the stream info.
*/
inline const FlacMetaDataBlockStreamInfo &FlacToOggMappingHeader::streamInfo() const
{
return m_streamInfo;
}
}
#endif // MEDIA_FLACIDENTIFICATIONHEADER_H

View File

@ -247,15 +247,20 @@ inline std::vector<std::unique_ptr<TrackType> > &GenericContainer<FileInfoType,
template <class FileInfoType, class TagType, class TrackType, class ElementType>
TagType *GenericContainer<FileInfoType, TagType, TrackType, ElementType>::createTag(const TagTarget &target)
{
if(!target.isEmpty()) {
for(auto &tag : m_tags) {
if(tag->target() == target) {
return tag.get();
// check whether a tag matching the specified target is already assigned
if(!m_tags.empty()) {
if(!target.isEmpty() && m_tags.front()->supportsTarget()) {
for(auto &tag : m_tags) {
if(tag->target() == target) {
return tag.get();
}
}
} else {
return m_tags.front().get();
}
} else if(!m_tags.empty()) {
return m_tags.front().get();
}
// a new tag must be created
m_tags.emplace_back(std::make_unique<TagType>());
auto &tag = m_tags.back();
tag->setTarget(target);

View File

@ -413,11 +413,17 @@ Mp4TagFieldMaker::Mp4TagFieldMaker(Mp4TagField &field) :
try {
// try to use appropriate raw data type
m_rawDataType = m_field.appropriateRawDataType();
} catch(Failure &) {
} catch(const Failure &) {
// unable to obtain appropriate raw data type
// assume utf-8 text
m_rawDataType = RawDataType::Utf8;
m_field.addNotification(NotificationType::Warning, "It was not possible to find an appropriate raw data type id. UTF-8 will be assumed.", context);
if(m_field.id() == Mp4TagAtomIds::Cover) {
// assume JPEG image
m_rawDataType = RawDataType::Utf8;
m_field.addNotification(NotificationType::Warning, "It was not possible to find an appropriate raw data type id. JPEG image will be assumed.", context);
} else {
// assume UTF-8 text
m_rawDataType = RawDataType::Utf8;
m_field.addNotification(NotificationType::Warning, "It was not possible to find an appropriate raw data type id. UTF-8 will be assumed.", context);
}
}
try {

View File

@ -1,5 +1,7 @@
#include "./oggcontainer.h"
#include "../flac/flacmetadata.h"
#include "../mediafileinfo.h"
#include "../backuphelper.h"
@ -11,6 +13,25 @@ using namespace IoUtilities;
namespace Media {
/*!
* \class Media::OggVorbisComment
* \brief Specialization of Media::VorbisComment for Vorbis comments inside an OGG stream.
*/
const char *OggVorbisComment::typeName() const
{
switch(m_oggParams.streamFormat) {
case GeneralMediaFormat::Flac:
return "Vorbis comment (in FLAC stream)";
case GeneralMediaFormat::Opus:
return "Vorbis comment (in Opus stream)";
case GeneralMediaFormat::Theora:
return "Vorbis comment (in Theora stream)";
default:
return "Vorbis comment";
}
}
/*!
* \class Media::OggContainer
* \brief Implementation of Media::AbstractContainer for OGG files.
@ -20,7 +41,7 @@ namespace Media {
* \brief Constructs a new container for the specified \a stream at the specified \a startOffset.
*/
OggContainer::OggContainer(MediaFileInfo &fileInfo, uint64 startOffset) :
GenericContainer<MediaFileInfo, VorbisComment, OggStream, OggPage>(fileInfo, startOffset),
GenericContainer<MediaFileInfo, OggVorbisComment, OggStream, OggPage>(fileInfo, startOffset),
m_iterator(fileInfo.stream(), startOffset, fileInfo.size()),
m_validateChecksums(false)
{}
@ -36,28 +57,36 @@ void OggContainer::reset()
/*!
* \brief Creates a new tag.
* \sa AbstractContainer::createTag()
* \remarks Tracks must be parsed before because tags are stored on track level!
* \remarks
* - Tracks must be parsed before because tags are stored on track level!
* - The track can be specified via the \a target argument. However, only the first track of tracks() array is considered.
* - If tracks() array of \a target is empty, a random track will be picked.
* - Vorbis streams should always have a tag assigned yet. However, this
* methods allows creation of a tag if none has been assigned yet.
* - FLAC streams should always have a tag assigned yet and this method
* does NOT allow to create a tag in this case.
*/
VorbisComment *OggContainer::createTag(const TagTarget &target)
OggVorbisComment *OggContainer::createTag(const TagTarget &target)
{
if(!target.isEmpty()) {
// targets are not supported here, so the specified target should be empty
// -> just be consistent with generic implementation here
if(!target.tracks().empty()) {
// return the tag for the first matching track ID
for(auto &tag : m_tags) {
if(tag->target() == target && !tag->oggParams().removed) {
if(!tag->target().tracks().empty() && tag->target().tracks().front() == target.tracks().front() && !tag->oggParams().removed) {
return tag.get();
}
}
// not tag found -> try to re-use a tag which has been flagged as removed
for(auto &tag : m_tags) {
if(tag->target() == target) {
if(!tag->target().tracks().empty() && tag->target().tracks().front() == target.tracks().front()) {
tag->oggParams().removed = false;
return tag.get();
}
}
} else if(VorbisComment *comment = tag(0)) {
comment->oggParams().removed = false;
} else if(OggVorbisComment *comment = tag(0)) {
// no track ID specified -> just return the first tag (if one exists)
return comment;
} else if(!m_tags.empty()) {
// no track ID specified -> just return the first tag (try to re-use a tag which has been flagged as removed)
m_tags.front()->oggParams().removed = false;
return m_tags.front().get();
}
@ -65,27 +94,29 @@ VorbisComment *OggContainer::createTag(const TagTarget &target)
// a new tag needs to be created
// -> determine an appropriate track for the tag
// -> just use the first Vorbis/Opus track
// -> TODO: provide interface for specifying a specific track
for(const auto &track : m_tracks) {
switch(track->format().general) {
case GeneralMediaFormat::Vorbis:
case GeneralMediaFormat::Opus:
// check whether start page has a valid value
if(track->startPage() < m_iterator.pages().size()) {
ariseComment(track->startPage(), static_cast<size_t>(-1), track->format().general);
m_tags.back()->setTarget(target); // also for consistency
return m_tags.back().get();
} else {
// TODO: error handling?
if(target.tracks().empty() || target.tracks().front() == track->id()) {
switch(track->format().general) {
case GeneralMediaFormat::Vorbis:
case GeneralMediaFormat::Opus:
// check whether start page has a valid value
if(track->startPage() < m_iterator.pages().size()) {
announceComment(track->startPage(), static_cast<size_t>(-1), false, track->format().general);
m_tags.back()->setTarget(target);
return m_tags.back().get();
} else {
// TODO: error handling?
}
default:
;
}
default:
;
// TODO: allow adding tags to FLAC tracks (not really important, because a tag should always be present)
}
}
return nullptr;
}
VorbisComment *OggContainer::tag(size_t index)
OggVorbisComment *OggContainer::tag(size_t index)
{
size_t i = 0;
for(const auto &tag : m_tags) {
@ -205,7 +236,10 @@ void OggContainer::internalParseTags()
case GeneralMediaFormat::Opus:
// skip header (has already been detected by OggStream)
m_iterator.seekForward(8);
comment->parse(m_iterator, true);
comment->parse(m_iterator, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte);
break;
case GeneralMediaFormat::Flac:
comment->parse(m_iterator, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, 4);
break;
default:
addNotification(NotificationType::Critical, "Stream format not supported.", "parsing tags from OGG streams");
@ -215,10 +249,21 @@ void OggContainer::internalParseTags()
}
}
void OggContainer::ariseComment(std::size_t pageIndex, std::size_t segmentIndex, GeneralMediaFormat mediaFormat)
/*!
* \brief Announces the existence of a Vorbis comment.
*
* The start offset of the comment is specified by \a pageIndex and \a segmentIndex.
*
* The format of the stream the comment belongs to is specified by \a mediaFormat.
* Valid values are GeneralMediaFormat::Vorbis, GeneralMediaFormat::Opus
* and GeneralMediaFormat::Flac.
*
* \remarks This method is called by OggStream when parsing the header.
*/
void OggContainer::announceComment(std::size_t pageIndex, std::size_t segmentIndex, bool lastMetaDataBlock, GeneralMediaFormat mediaFormat)
{
m_tags.emplace_back(make_unique<VorbisComment>());
m_tags.back()->oggParams().set(pageIndex, segmentIndex, mediaFormat);
m_tags.emplace_back(make_unique<OggVorbisComment>());
m_tags.back()->oggParams().set(pageIndex, segmentIndex, lastMetaDataBlock, mediaFormat);
}
void OggContainer::internalParseTracks()
@ -240,7 +285,7 @@ void OggContainer::internalParseTracks()
* \brief Writes the specified \a comment with the given \a params to the specified \a buffer and
* adds the number of bytes written to \a newSegmentSizes.
*/
void makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> &copyHelper, vector<uint32> &newSegmentSizes, VorbisComment *comment, OggParameter *params)
void OggContainer::makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> &copyHelper, vector<uint32> &newSegmentSizes, VorbisComment *comment, OggParameter *params)
{
auto offset = buffer.tellp();
switch(params->streamFormat) {
@ -250,9 +295,29 @@ void makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> &copyHelpe
case GeneralMediaFormat::Opus:
ConversionUtilities::BE::getBytes(0x4F70757354616773u, copyHelper.buffer());
buffer.write(copyHelper.buffer(), 8);
comment->make(buffer, true);
comment->make(buffer, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte);
break;
default:
case GeneralMediaFormat::Flac: {
// Vorbis comment must be wrapped in "METADATA_BLOCK_HEADER"
FlacMetaDataBlockHeader header;
header.setLast(params->lastMetaDataBlock);
header.setType(FlacMetaDataBlockType::VorbisComment);
// write the header later, when the size is known
buffer.write(copyHelper.buffer(), 4);
comment->make(buffer, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte);
// finally make the header
header.setDataSize(buffer.tellp() - offset - 4);
if(header.dataSize() > 0xFFFFFF) {
addNotification(NotificationType::Critical, "Size of Vorbis comment exceeds size limit for FLAC \"METADATA_BLOCK_HEADER\".", "making Vorbis Comment");
}
buffer.seekp(offset);
header.makeHeader(buffer);
buffer.seekp(header.dataSize(), ios_base::cur);
break;
} default:
;
}
newSegmentSizes.push_back(buffer.tellp() - offset);
@ -291,7 +356,7 @@ void OggContainer::internalMakeFile()
try {
// prepare iterating comments
VorbisComment *currentComment;
OggVorbisComment *currentComment;
OggParameter *currentParams;
auto tagIterator = m_tags.cbegin(), tagEnd = m_tags.cend();
if(tagIterator != tagEnd) {
@ -428,6 +493,7 @@ void OggContainer::internalMakeFile()
stream().seekp(segmentSizesWritten, ios_base::cur);
// -> write actual page data
copyHelper.copy(buffer, stream(), currentSize);
++pageSequenceNumber;
}
}

View File

@ -12,11 +12,119 @@
#include <unordered_map>
#include <tuple>
namespace IoUtilities {
template<std::size_t bufferSize>
class CopyHelper;
}
namespace Media {
class MediaFileInfo;
class OggContainer;
class LIB_EXPORT OggContainer : public GenericContainer<MediaFileInfo, VorbisComment, OggStream, OggPage>
/*!
* \brief The OggParameter struct holds the OGG parameter for a VorbisComment.
*/
struct LIB_EXPORT OggParameter
{
OggParameter();
void set(std::size_t pageIndex, std::size_t segmentIndex, bool lastMetaDataBlock, GeneralMediaFormat streamFormat = GeneralMediaFormat::Vorbis);
std::size_t firstPageIndex;
std::size_t firstSegmentIndex;
std::size_t lastPageIndex;
std::size_t lastSegmentIndex;
bool lastMetaDataBlock;
GeneralMediaFormat streamFormat;
bool removed;
};
/*!
* \brief Creates new parameters.
* \remarks The OggContainer class is responsible for assigning sane values.
*/
inline OggParameter::OggParameter() :
firstPageIndex(0),
firstSegmentIndex(0),
lastPageIndex(0),
lastSegmentIndex(0),
lastMetaDataBlock(false),
streamFormat(GeneralMediaFormat::Vorbis),
removed(false)
{}
/*!
* \brief Sets the firstPageIndex/lastPageIndex, the firstSegmentIndex/lastSegmentIndex, whether the associated meta data block is the last one and the streamFormat.
* \remarks Whether the associated meta data block is the last one is only relevant for FLAC streams.
*/
inline void OggParameter::set(std::size_t pageIndex, std::size_t segmentIndex, bool lastMetaDataBlock, GeneralMediaFormat streamFormat)
{
firstPageIndex = lastPageIndex = pageIndex;
firstSegmentIndex = lastSegmentIndex = segmentIndex;
this->lastMetaDataBlock = lastMetaDataBlock;
this->streamFormat = streamFormat;
}
class LIB_EXPORT OggVorbisComment : public VorbisComment
{
friend class OggContainer;
public:
OggVorbisComment();
TagType type() const;
const char *typeName() const;
bool supportsTarget() const;
OggParameter &oggParams();
const OggParameter &oggParams() const;
private:
OggParameter m_oggParams;
};
/*!
* \brief Constructs a new OGG Vorbis comment.
*/
inline OggVorbisComment::OggVorbisComment()
{}
inline TagType OggVorbisComment::type() const
{
return TagType::OggVorbisComment;
}
/*!
* \brief Returns true; the target is used to specifiy the stream.
* \sa OggContainer::createTag(), TagTarget
*/
inline bool OggVorbisComment::supportsTarget() const
{
return true;
}
/*!
* \brief Returns the OGG parameter for the comment.
*
* Consists of first page index, first segment index, last page index, last segment index and tag index (in this order).
* These values are used and managed by the OggContainer class and do not affect the behavior of the VorbisComment instance.
*/
inline OggParameter &OggVorbisComment::oggParams()
{
return m_oggParams;
}
/*!
* \brief Returns the OGG parameter for the comment.
*
* Consists of first page index, first segment index, last page index, last segment index and tag index (in this order).
* These values are used and managed by the OggContainer class and do not affect the behavior of the VorbisComment instance.
*/
inline const OggParameter &OggVorbisComment::oggParams() const
{
return m_oggParams;
}
class LIB_EXPORT OggContainer : public GenericContainer<MediaFileInfo, OggVorbisComment, OggStream, OggPage>
{
friend class OggStream;
@ -28,8 +136,8 @@ public:
void setChecksumValidationEnabled(bool enabled);
void reset();
VorbisComment *createTag(const TagTarget &target);
VorbisComment *tag(std::size_t index);
OggVorbisComment *createTag(const TagTarget &target);
OggVorbisComment *tag(std::size_t index);
std::size_t tagCount() const;
bool removeTag(Tag *tag);
void removeAllTags();
@ -41,7 +149,8 @@ protected:
void internalMakeFile();
private:
void ariseComment(std::size_t pageIndex, std::size_t segmentIndex, GeneralMediaFormat mediaFormat = GeneralMediaFormat::Vorbis);
void announceComment(std::size_t pageIndex, std::size_t segmentIndex, bool lastMetaDataBlock, GeneralMediaFormat mediaFormat = GeneralMediaFormat::Vorbis);
void makeVorbisCommentSegment(std::stringstream &buffer, IoUtilities::CopyHelper<65307> &copyHelper, std::vector<uint32> &newSegmentSizes, VorbisComment *comment, OggParameter *params);
std::unordered_map<uint32, std::vector<std::unique_ptr<OggStream> >::size_type> m_streamsBySerialNo;

View File

@ -161,8 +161,9 @@ void OggIterator::read(char *buffer, size_t count)
*/
void OggIterator::seekForward(size_t count)
{
while(*this && count) {
uint32 available = currentSegmentSize() - m_bytesRead;
uint32 available = currentSegmentSize() - m_bytesRead;
while(*this) {
available = currentSegmentSize() - m_bytesRead;
if(count <= available) {
m_bytesRead += count;
return;

View File

@ -28,6 +28,7 @@ public:
bool isLastPage() const;
uint64 absoluteGranulePosition() const;
uint32 streamSerialNumber() const;
bool matchesStreamSerialNumber(uint32 streamSerialNumber) const;
uint32 sequenceNumber() const;
uint32 checksum() const;
byte segmentTableSize() const;
@ -155,6 +156,15 @@ inline uint32 OggPage::streamSerialNumber() const
return m_streamSerialNumber;
}
/*!
* \brief Returns whether the stream serial number of the current instance matches the specified one.
* \sa streamSerialNumber()
*/
inline bool OggPage::matchesStreamSerialNumber(uint32 streamSerialNumber) const
{
return m_streamSerialNumber == streamSerialNumber;
}
/*!
* \brief Returns the page sequence number.
*

View File

@ -6,6 +6,8 @@
#include "../opus/opusidentificationheader.h"
#include "../flac/flactooggmappingheader.h"
#include "../mediafileinfo.h"
#include "../exceptions.h"
#include "../mediaformat.h"
@ -13,8 +15,10 @@
#include <c++utilities/chrono/timespan.h>
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;
using namespace ChronoUtilities;
namespace Media {
@ -51,13 +55,15 @@ void OggStream::internalParseHeader()
m_id = firstPage.streamSerialNumber();
// ensure iterator is setup properly
iterator.setFilter(m_id);
iterator.setFilter(firstPage.streamSerialNumber());
iterator.setPageIndex(m_startPage);
// predicate for finding pages of this stream by its stream serial number
const auto pred = bind(&OggPage::matchesStreamSerialNumber, _1, firstPage.streamSerialNumber());
// iterate through segments using OggIterator
bool hasIdentificationHeader = false;
bool hasCommentHeader = false;
for(; iterator; ++iterator) {
// -> iterate through ALL segments to calculate the precise stream size (hence the out-commented part in the loop-condition)
for(bool hasIdentificationHeader = false, hasCommentHeader = false; iterator /* && (!hasIdentificationHeader && !hasCommentHeader) */; ++iterator) {
const uint32 currentSize = iterator.currentSegmentSize();
m_size += currentSize;
@ -78,7 +84,9 @@ void OggStream::internalParseHeader()
break;
default:
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
continue;
}
// check header type
switch(sig >> 56) {
case VorbisPackageTypes::Identification:
@ -99,12 +107,9 @@ void OggStream::internalParseHeader()
}
// determine sample count and duration if all pages have been fetched
if(iterator.areAllPagesFetched()) {
auto pred = [this] (const OggPage &page) -> bool {
return page.streamSerialNumber() == this->id();
};
const auto &pages = iterator.pages();
auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
const auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
const auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
if(firstPage != pages.cend() && lastPage != pages.crend()) {
m_sampleCount = lastPage->absoluteGranulePosition() - firstPage->absoluteGranulePosition();
m_duration = TimeSpan::fromSeconds(static_cast<double>(m_sampleCount) / m_samplingFrequency);
@ -118,7 +123,7 @@ void OggStream::internalParseHeader()
case VorbisPackageTypes::Comments:
// Vorbis comment found -> notify container about comment
if(!hasCommentHeader) {
m_container.ariseComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), GeneralMediaFormat::Vorbis);
m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), false, GeneralMediaFormat::Vorbis);
hasCommentHeader = true;
} else {
addNotification(NotificationType::Critical, "Vorbis comment header appears more then once. Oversupplied occurrence will be ignored.", context);
@ -142,6 +147,7 @@ void OggStream::internalParseHeader()
break;
default:
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
continue;
}
if(!hasIdentificationHeader) {
// parse identification header
@ -152,12 +158,9 @@ void OggStream::internalParseHeader()
m_samplingFrequency = ind.sampleRate();
// determine sample count and duration if all pages have been fetched
if(iterator.areAllPagesFetched()) {
auto pred = [this] (const OggPage &page) -> bool {
return page.streamSerialNumber() == this->id();
};
const auto &pages = iterator.pages();
auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
const auto firstPage = find_if(pages.cbegin(), pages.cend(), pred);
const auto lastPage = find_if(pages.crbegin(), pages.crend(), pred);
if(firstPage != pages.cend() && lastPage != pages.crend()) {
m_sampleCount = lastPage->absoluteGranulePosition() - firstPage->absoluteGranulePosition();
// must apply "pre-skip" here do calculate effective sample count and duration?
@ -186,15 +189,64 @@ void OggStream::internalParseHeader()
break;
default:
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
continue;
}
// notify container about comment
if(!hasCommentHeader) {
m_container.ariseComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), GeneralMediaFormat::Opus);
m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), false, GeneralMediaFormat::Opus);
hasCommentHeader = true;
} else {
addNotification(NotificationType::Critical, "Opus tags/comment header appears more then once. Oversupplied occurrence will be ignored.", context);
}
} else if((sig & 0xFFFFFFFFFF000000u) == 0x7F464C4143000000u) {
// FLAC header detected
// set FLAC as format
switch(m_format.general) {
case GeneralMediaFormat::Unknown:
m_format = GeneralMediaFormat::Flac;
m_mediaType = MediaType::Audio;
break;
case GeneralMediaFormat::Flac:
break;
default:
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
continue;
}
if(!hasIdentificationHeader) {
// parse FLAC-to-Ogg mapping header
FlacToOggMappingHeader mapping;
const FlacMetaDataBlockStreamInfo &streamInfo = mapping.streamInfo();
mapping.parseHeader(iterator);
m_bitsPerSample = streamInfo.bitsPerSample();
m_channelCount = streamInfo.channelCount();
m_samplingFrequency = streamInfo.samplingFrequency();
m_sampleCount = streamInfo.totalSampleCount();
hasIdentificationHeader = true;
} else {
addNotification(NotificationType::Critical, "FLAC-to-Ogg mapping header appears more then once. Oversupplied occurrence will be ignored.", context);
}
if(!hasCommentHeader) {
// a Vorbis comment should be following
if(++iterator) {
char buff[4];
iterator.read(buff, 4);
FlacMetaDataBlockHeader header;
header.parseHeader(buff);
if(header.type() == FlacMetaDataBlockType::VorbisComment) {
m_container.announceComment(iterator.currentPageIndex(), iterator.currentSegmentIndex(), header.isLast(), GeneralMediaFormat::Flac);
hasCommentHeader = true;
} else {
addNotification(NotificationType::Critical, "OGG page after FLAC-to-Ogg mapping header doesn't contain Vorbis comment.", context);
}
} else {
addNotification(NotificationType::Critical, "No more OGG pages after FLAC-to-Ogg mapping header (Vorbis comment expected).", context);
}
}
} else if((sig & 0x00ffffffffffff00u) == 0x007468656F726100u) {
// Theora header detected
// set Theora as format
@ -207,10 +259,18 @@ void OggStream::internalParseHeader()
break;
default:
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
continue;
}
// TODO: read more information about Theora stream
} // currently only Vorbis, Opus and Theora can be detected, TODO: detect more formats
} // TODO: reduce code duplication
} else {
// just ignore segments of only 8 byte or even less
// TODO: print warning?
}
// TODO: reduce code duplication
}
if(m_duration.isNull() && m_size && m_bitrate) {

View File

@ -4,8 +4,6 @@
#include <c++utilities/application/global.h>
#include <c++utilities/conversion/types.h>
#include <istream>
namespace Media {
class OggIterator;
@ -34,7 +32,7 @@ private:
};
/*!
* \brief Constructs a new vorbis identification header.
* \brief Constructs a new Opus identification header.
*/
inline OpusIdentificationHeader::OpusIdentificationHeader() :
m_version(0),

4
size.h
View File

@ -24,7 +24,7 @@ public:
void setHeight(uint32 value);
bool constexpr isNull() const;
bool constexpr operator==(const Size &other);
bool constexpr operator==(const Size &other) const;
std::string toString() const;
private:
@ -91,7 +91,7 @@ inline constexpr bool Size::isNull() const
/*!
* \brief Returns whether this instance equals \a other.
*/
inline constexpr bool Size::operator==(const Size &other)
inline constexpr bool Size::operator==(const Size &other) const
{
return (m_width == other.m_width) && (m_height == other.m_height);
}

6
tag.h
View File

@ -26,7 +26,8 @@ enum class TagType : unsigned int
Id3v2Tag = 0x02, /**< The tag is a Media::Id3v2Tag. */
Mp4Tag = 0x04, /**< The tag is a Media::Mp4Tag. */
MatroskaTag = 0x08, /**< The tag is a Media::MatroskaTag. */
VorbisComment = 0x10 /**< The tag is a Media::VorbisComment. */
VorbisComment = 0x10, /**< The tag is a Media::VorbisComment. */
OggVorbisComment = 0x20 /**< The tag is a Media::OggVorbisComment. */
};
/*!
@ -216,8 +217,7 @@ inline uint32 Tag::size() const
}
/*!
* \brief Returns an indication whether a target is supported
* by the tag.
* \brief Returns an indication whether a target is supported by the tag.
*
* If no target is supported, setting a target using setTarget()
* has no effect when saving the tag.

View File

@ -11,6 +11,23 @@ using namespace ConversionUtilities;
namespace Media {
/*!
* \class Media::TagTarget
* \brief The TagTarget class specifies the target of a tag.
*
* Tags might only target a specific track, chapter, ...
*
* Specifying a target is currently only fully supported by Matroska.
*
* Since Ogg saves tags at stream level, the stream can be specified
* by passing a TagTarget instance to OggContainer::createTag().
* However, only the first track in the tracks() array is considered
* and any other values are just ignored.
*
* In any other tag formats, the specified target is (currently)
* completely ignored.
*/
/*!
* \brief Returns the string representation of the current instance.
*/

View File

@ -9,9 +9,6 @@
namespace Media {
/*!
* \brief The TagTarget class stores target information.
*/
class LIB_EXPORT TagTarget
{
public:

View File

@ -20,7 +20,7 @@ namespace Media {
/*!
* \class Media::VorbisComment
* \brief Implementation of Media::Tag for the Vorbis comment.
* \brief Implementation of Media::Tag for Vorbis comments.
*/
const TagValue &VorbisComment::value(KnownField field) const
@ -106,16 +106,17 @@ KnownField VorbisComment::knownField(const string &id) const
* \throws Throws Media::Failure or a derived exception when a parsing
* error occurs.
*/
void VorbisComment::parse(OggIterator &iterator, bool skipSignature)
void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, size_t offset)
{
// prepare parsing
invalidateStatus();
static const string context("parsing Vorbis comment");
iterator.stream().seekg(iterator.currentSegmentOffset());
auto startOffset = iterator.currentSegmentOffset();
auto startOffset = iterator.currentSegmentOffset() + offset;
iterator.seekForward(offset);
try {
// read signature: 0x3 + "vorbis"
char sig[8];
bool skipSignature = flags & VorbisCommentFlags::NoSignature;
if(!skipSignature) {
iterator.read(sig, 7);
skipSignature = (ConversionUtilities::BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
@ -124,11 +125,12 @@ void VorbisComment::parse(OggIterator &iterator, bool skipSignature)
// read vendor (length prefixed string)
{
iterator.read(sig, 4);
auto vendorSize = LE::toUInt32(sig);
const auto vendorSize = LE::toUInt32(sig);
if(iterator.currentCharacterOffset() + vendorSize <= iterator.streamSize()) {
auto buff = make_unique<char []>(vendorSize);
iterator.read(buff.get(), vendorSize);
m_vendor = string(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();
@ -153,7 +155,9 @@ void VorbisComment::parse(OggIterator &iterator, bool skipSignature)
addNotifications(field);
field.invalidateNotifications();
}
iterator.seekForward(1); // skip framing
if(!(flags & VorbisCommentFlags::NoFramingByte)) {
iterator.seekForward(1); // skip framing byte
}
m_size = static_cast<uint32>(static_cast<uint64>(iterator.currentCharacterOffset()) - startOffset);
} else {
addNotification(NotificationType::Critical, "Signature is invalid.", context);
@ -173,7 +177,7 @@ void VorbisComment::parse(OggIterator &iterator, bool skipSignature)
* \throws Throws Media::Failure or a derived exception when a making
* error occurs.
*/
void VorbisComment::make(std::ostream &stream, bool noSignature)
void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags)
{
// prepare making
invalidateStatus();
@ -185,7 +189,7 @@ void VorbisComment::make(std::ostream &stream, bool noSignature)
addNotification(NotificationType::Warning, "Can not convert the assigned vendor to string.", context);
}
BinaryWriter writer(&stream);
if(!noSignature) {
if(!(flags & VorbisCommentFlags::NoSignature)) {
// write signature
static const char sig[7] = {0x03, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73};
stream.write(sig, sizeof(sig));
@ -201,7 +205,7 @@ void VorbisComment::make(std::ostream &stream, bool noSignature)
if(!field.value().isEmpty()) {
try {
field.make(writer);
} catch(Failure &) {
} catch(const Failure &) {
// nothing to do here since notifications will be added anyways
}
// add making notifications
@ -210,7 +214,9 @@ void VorbisComment::make(std::ostream &stream, bool noSignature)
}
}
// write framing byte
stream.put(0x01);
if(!(flags & VorbisCommentFlags::NoFramingByte)) {
stream.put(0x01);
}
}
}

View File

@ -13,48 +13,27 @@ class OggIterator;
class VorbisComment;
/*!
* \brief The OggParameter struct holds the OGG parameter for a VorbisComment.
* \brief The VorbisCommentFlags enum specifies flags which controls parsing and making of Vorbis comments.
*/
struct LIB_EXPORT OggParameter
enum class VorbisCommentFlags : byte
{
OggParameter();
void set(std::size_t pageIndex, std::size_t segmentIndex, GeneralMediaFormat streamFormat = GeneralMediaFormat::Vorbis);
std::size_t firstPageIndex;
std::size_t firstSegmentIndex;
std::size_t lastPageIndex;
std::size_t lastSegmentIndex;
GeneralMediaFormat streamFormat;
bool removed;
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. */
};
/*!
* \brief Creates new parameters.
* \remarks The OggContainer class is responsible for assigning sane values.
*/
inline OggParameter::OggParameter() :
firstPageIndex(0),
firstSegmentIndex(0),
lastPageIndex(0),
lastSegmentIndex(0),
streamFormat(GeneralMediaFormat::Vorbis), // default to Vorbis here
removed(false)
{}
/*!
* \brief Sets firstPageIndex/lastPageIndex, firstSegmentIndex/lastSegmentIndex and streamFormat.
*/
inline void OggParameter::set(std::size_t pageIndex, std::size_t segmentIndex, GeneralMediaFormat streamFormat)
inline bool operator &(VorbisCommentFlags lhs, VorbisCommentFlags rhs)
{
firstPageIndex = lastPageIndex = pageIndex;
firstSegmentIndex = lastSegmentIndex = segmentIndex;
this->streamFormat = streamFormat;
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>
{
friend class OggContainer;
public:
VorbisComment();
@ -68,17 +47,14 @@ public:
std::string fieldId(KnownField field) const;
KnownField knownField(const std::string &id) const;
void parse(OggIterator &iterator, bool skipSignature = false);
void make(std::ostream &stream, bool noSignature = false);
void parse(OggIterator &iterator, VorbisCommentFlags flags = VorbisCommentFlags::None, std::size_t offset = 0);
void make(std::ostream &stream, VorbisCommentFlags flags = VorbisCommentFlags::None);
const TagValue &vendor() const;
void setVendor(const TagValue &vendor);
OggParameter &oggParams();
const OggParameter &oggParams() const;
private:
TagValue m_vendor;
OggParameter m_oggParams;
};
/*!
@ -94,15 +70,7 @@ inline TagType VorbisComment::type() const
inline const char *VorbisComment::typeName() const
{
switch(m_oggParams.streamFormat) {
case GeneralMediaFormat::Opus:
return "Opus comment";
case GeneralMediaFormat::Theora:
return "Theora comment";
default:
// just assume Vorbis otherwise
return "Vorbis comment";
}
return "Vorbis comment";
}
inline TagTextEncoding VorbisComment::proposedTextEncoding() const
@ -125,7 +93,7 @@ inline const TagValue &VorbisComment::vendor() const
}
/*!
* \brief Returns the vendor.
* \brief Sets the vendor.
* \remarks Also accessable via setValue(KnownField::Vendor, vendor).
*/
inline void VorbisComment::setVendor(const TagValue &vendor)
@ -133,28 +101,6 @@ inline void VorbisComment::setVendor(const TagValue &vendor)
m_vendor = vendor;
}
/*!
* \brief Returns the OGG parameter for the comment.
*
* Consists of first page index, first segment index, last page index, last segment index and tag index (in this order).
* These values are used and managed by the OggContainer class and do not affect the behavior of the VorbisComment instance.
*/
inline OggParameter &VorbisComment::oggParams()
{
return m_oggParams;
}
/*!
* \brief Returns the OGG parameter for the comment.
*
* Consists of first page index, first segment index, last page index, last segment index and tag index (in this order).
* These values are used and managed by the OggContainer class and do not affect the behavior of the VorbisComment instance.
*/
inline const OggParameter &VorbisComment::oggParams() const
{
return m_oggParams;
}
}
#endif // MEDIA_VORBISCOMMENT_H

View File

@ -1,6 +1,8 @@
#include "./vorbiscommentfield.h"
#include "./vorbiscommentids.h"
#include "../flac/flacmetadata.h"
#include "../ogg/oggiterator.h"
#include "../id3/id3v2frame.h"
@ -71,21 +73,12 @@ void VorbisCommentField::parse(OggIterator &iterator)
// extract cover value
try {
auto decoded = decodeBase64(data.get() + idSize + 1, size - idSize - 1);
stringstream ss(ios_base::in | ios_base::out | ios_base::binary);
ss.exceptions(ios_base::failbit | ios_base::badbit);
ss.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
BinaryReader reader(&ss);
setTypeInfo(reader.readUInt32BE());
auto size = reader.readUInt32BE();
value().setMimeType(reader.readString(size));
size = reader.readUInt32BE();
value().setDescription(reader.readString(size));
// skip width, height, color depth, number of colors used
ss.seekg(4 * 4, ios_base::cur);
size = reader.readUInt32BE();
auto data = make_unique<char[]>(size);
ss.read(data.get(), size);
value().assignData(move(data), size, TagDataType::Picture);
stringstream bufferStream(ios_base::in | ios_base::out | ios_base::binary);
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);
setTypeInfo(pictureBlock.pictureType());
} catch (const ios_base::failure &) {
addNotification(NotificationType::Critical, "An IO error occured when reading the METADATA_BLOCK_PICTURE struct.", context);
throw Failure();
@ -95,7 +88,7 @@ void VorbisCommentField::parse(OggIterator &iterator)
}
} else if(id().size() + 1 < size) {
// extract other values (as string)
setValue(string(data.get() + idSize + 1, size - idSize - 1));
setValue(TagValue(string(data.get() + idSize + 1, size - idSize - 1), TagTextEncoding::Utf8));
}
} else {
addNotification(NotificationType::Critical, "Field is truncated.", context);
@ -127,24 +120,17 @@ void VorbisCommentField::make(BinaryWriter &writer)
throw InvalidDataException();
}
try {
uint32 dataSize = 32 + value().mimeType().size() + value().description().size() + value().dataSize();
auto buffer = make_unique<char[]>(dataSize);
stringstream ss(ios_base::in | ios_base::out | ios_base::binary);
ss.exceptions(ios_base::failbit | ios_base::badbit);
ss.rdbuf()->pubsetbuf(buffer.get(), dataSize);
BinaryWriter writer(&ss);
writer.writeUInt32BE(typeInfo());
writer.writeUInt32BE(value().mimeType().size());
writer.writeString(value().mimeType());
writer.writeUInt32BE(value().description().size());
writer.writeString(value().description());
writer.writeUInt32BE(0); // skip width
writer.writeUInt32BE(0); // skip height
writer.writeUInt32BE(0); // skip color depth
writer.writeUInt32BE(0); // skip number of colors used
writer.writeUInt32BE(value().dataSize());
writer.write(value().dataPointer(), value().dataSize());
valueString = encodeBase64(reinterpret_cast<byte *>(buffer.get()), dataSize);
FlacMetaDataBlockPicture pictureBlock(value());
pictureBlock.setPictureType(typeInfo());
const auto requiredSize = pictureBlock.requiredSize();
auto buffer = make_unique<char[]>(requiredSize);
stringstream bufferStream(ios_base::in | ios_base::out | ios_base::binary);
bufferStream.exceptions(ios_base::failbit | ios_base::badbit);
bufferStream.rdbuf()->pubsetbuf(buffer.get(), requiredSize);
pictureBlock.make(bufferStream);
valueString = encodeBase64(reinterpret_cast<byte *>(buffer.get()), requiredSize);
} catch (const ios_base::failure &) {
addNotification(NotificationType::Critical, "An IO error occured when writing the METADATA_BLOCK_PICTURE struct.", context);
throw Failure();

View File

@ -4,8 +4,6 @@
#include <c++utilities/application/global.h>
#include <c++utilities/conversion/types.h>
#include <istream>
namespace Media {
class OggIterator;
@ -38,7 +36,7 @@ private:
};
/*!
* \brief Constructs a new vorbis identification header.
* \brief Constructs a new Vorbis identification header.
*/
inline VorbisIdentificationHeader::VorbisIdentificationHeader() :
m_version(0),