Support FLAC in Ogg
This commit is contained in:
parent
8d8322948d
commit
bbafd16dcc
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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> ©Helper, vector<uint32> &newSegmentSizes, VorbisComment *comment, OggParameter *params)
|
||||
void OggContainer::makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> ©Helper, vector<uint32> &newSegmentSizes, VorbisComment *comment, OggParameter *params)
|
||||
{
|
||||
auto offset = buffer.tellp();
|
||||
switch(params->streamFormat) {
|
||||
|
@ -250,9 +295,29 @@ void makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> ©Helpe
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> ©Helper, std::vector<uint32> &newSegmentSizes, VorbisComment *comment, OggParameter *params);
|
||||
|
||||
std::unordered_map<uint32, std::vector<std::unique_ptr<OggStream> >::size_type> m_streamsBySerialNo;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
4
size.h
|
@ -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
6
tag.h
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -9,9 +9,6 @@
|
|||
|
||||
namespace Media {
|
||||
|
||||
/*!
|
||||
* \brief The TagTarget class stores target information.
|
||||
*/
|
||||
class LIB_EXPORT TagTarget
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue