fixed some OGG/Vorbis related issues
This commit is contained in:
parent
4a43df7d8f
commit
b8e8dcd778
|
@ -112,7 +112,6 @@ set(SRC_FILES
|
|||
tagvalue.cpp
|
||||
vorbis/vorbiscomment.cpp
|
||||
vorbis/vorbiscommentfield.cpp
|
||||
vorbis/vorbiscommentids.cpp
|
||||
vorbis/vorbisidentificationheader.cpp
|
||||
wav/waveaudiostream.cpp
|
||||
id3/id3genres.cpp
|
||||
|
|
|
@ -48,5 +48,6 @@ The tagparser library depends on c++utilities and is built in the same way.
|
|||
It also depends on zlib.
|
||||
|
||||
## TODO
|
||||
- Support more tag formats (EXIF, PDF metadata, ...).
|
||||
- Support more formats (EXIF, PDF metadata, Theora, ...).
|
||||
- Allow adding tags to specific streams when dealing with OGG.
|
||||
- Do tests with Matroska files which have multiple segments.
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "./abstractcontainer.h"
|
||||
|
||||
#include <c++utilities/misc/memory.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
@ -202,7 +204,7 @@ inline const std::vector<std::unique_ptr<TagType> > &GenericContainer<FileInfoTy
|
|||
*
|
||||
* The tags need to be parsed before (see parseTags()).
|
||||
*
|
||||
* The container keeps ownership over the returned tags.
|
||||
* The container keeps ownership over the returned tags. Do not push or remove elements to the returned vector.
|
||||
*
|
||||
* \sa areTagsParsed()
|
||||
*/
|
||||
|
@ -232,7 +234,7 @@ inline const std::vector<std::unique_ptr<TrackType> > &GenericContainer<FileInfo
|
|||
*
|
||||
* The tags need to be parsed before (see parseTracks()).
|
||||
*
|
||||
* The container keeps ownership over the returned tracks.
|
||||
* The container keeps ownership over the returned tracks. Do not push or remove elements to the returned vector.
|
||||
*
|
||||
* \sa areTracksParsed()
|
||||
*/
|
||||
|
@ -243,7 +245,7 @@ inline std::vector<std::unique_ptr<TrackType> > &GenericContainer<FileInfoType,
|
|||
}
|
||||
|
||||
template <class FileInfoType, class TagType, class TrackType, class ElementType>
|
||||
inline TagType *GenericContainer<FileInfoType, TagType, TrackType, ElementType>::createTag(const TagTarget &target)
|
||||
TagType *GenericContainer<FileInfoType, TagType, TrackType, ElementType>::createTag(const TagTarget &target)
|
||||
{
|
||||
if(!target.isEmpty()) {
|
||||
for(auto &tag : m_tags) {
|
||||
|
@ -254,7 +256,7 @@ inline TagType *GenericContainer<FileInfoType, TagType, TrackType, ElementType>:
|
|||
} else if(!m_tags.empty()) {
|
||||
return m_tags.front().get();
|
||||
}
|
||||
m_tags.emplace_back(new TagType());
|
||||
m_tags.emplace_back(std::make_unique<TagType>());
|
||||
auto &tag = m_tags.back();
|
||||
tag->setTarget(target);
|
||||
return tag.get();
|
||||
|
@ -308,7 +310,7 @@ bool GenericContainer<FileInfoType, TagType, TrackType, ElementType>::removeTrac
|
|||
bool removed = false;
|
||||
if(areTracksParsed() && supportsTrackModifications() && !m_tracks.empty()) {
|
||||
for(auto i = m_tracks.end() - 1, begin = m_tracks.begin(); ; --i) {
|
||||
if(static_cast<void *>(i->get()) == static_cast<void *>(track)) {
|
||||
if(static_cast<AbstractTrack *>(i->get()) == track) {
|
||||
i->release();
|
||||
m_tracks.erase(i);
|
||||
removed = true;
|
||||
|
|
|
@ -30,10 +30,126 @@ OggContainer::~OggContainer()
|
|||
|
||||
void OggContainer::reset()
|
||||
{
|
||||
m_commentTable.clear();
|
||||
m_iterator.reset();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Creates a new tag.
|
||||
* \sa AbstractContainer::createTag()
|
||||
* \remarks Tracks must be parsed before because tags are stored on track level!
|
||||
*/
|
||||
VorbisComment *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
|
||||
for(auto &tag : m_tags) {
|
||||
if(tag->target() == target && !tag->oggParams().removed) {
|
||||
return tag.get();
|
||||
}
|
||||
}
|
||||
for(auto &tag : m_tags) {
|
||||
if(tag->target() == target) {
|
||||
tag->oggParams().removed = false;
|
||||
return tag.get();
|
||||
}
|
||||
}
|
||||
} else if(VorbisComment *comment = tag(0)) {
|
||||
comment->oggParams().removed = false;
|
||||
return comment;
|
||||
} else if(!m_tags.empty()) {
|
||||
m_tags.front()->oggParams().removed = false;
|
||||
return m_tags.front().get();
|
||||
}
|
||||
|
||||
// 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?
|
||||
}
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VorbisComment *OggContainer::tag(size_t index)
|
||||
{
|
||||
size_t i = 0;
|
||||
for(const auto &tag : m_tags) {
|
||||
if(!tag->oggParams().removed) {
|
||||
if(index == i) {
|
||||
return tag.get();
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t OggContainer::tagCount() const
|
||||
{
|
||||
size_t count = 0;
|
||||
for(const auto &tag : m_tags) {
|
||||
if(!tag->oggParams().removed) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Actually just flags the specified \a tag as removed and clears all assigned fields.
|
||||
*
|
||||
* This specialization is neccessary because completeley removing the tag whould also
|
||||
* remove the OGG parameter which are needed when appying the changes.
|
||||
*
|
||||
* \remarks Seems like common players aren't able to play Vorbis when no comment is present.
|
||||
* So do NOT use this method to remove tags from Vorbis, just call removeAllFields() on \a tag.
|
||||
* \sa AbstractContainer::removeTag()
|
||||
*/
|
||||
bool OggContainer::removeTag(Tag *tag)
|
||||
{
|
||||
for(auto &existingTag : m_tags) {
|
||||
if(static_cast<Tag *>(existingTag.get()) == tag) {
|
||||
existingTag->removeAllFields();
|
||||
existingTag->oggParams().removed = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Actually just flags all tags as removed and clears all assigned fields.
|
||||
*
|
||||
* This specialization is neccessary because completeley removing the tag whould also
|
||||
* remove the OGG parameter which are needed when appying the changes.
|
||||
*
|
||||
* \remarks Seems like common players aren't able to play Vorbis when no comment is present.
|
||||
* So do NOT use this method to remove tags from Vorbis, just call removeAllFields() on all tags.
|
||||
* \sa AbstractContainer::removeAllTags()
|
||||
*/
|
||||
void OggContainer::removeAllTags()
|
||||
{
|
||||
for(auto &existingTag : m_tags) {
|
||||
existingTag->removeAllFields();
|
||||
existingTag->oggParams().removed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void OggContainer::internalParseHeader()
|
||||
{
|
||||
static const string context("parsing OGG bitstream header");
|
||||
|
@ -68,60 +184,80 @@ void OggContainer::internalParseHeader()
|
|||
} catch(const TruncatedDataException &) {
|
||||
// thrown when page exceeds max size
|
||||
addNotification(NotificationType::Critical, "The OGG file is truncated.", context);
|
||||
throw;
|
||||
} catch(const InvalidDataException &) {
|
||||
// thrown when first 4 byte do not match capture pattern
|
||||
addNotification(NotificationType::Critical, "Capture pattern \"OggS\" at " + ConversionUtilities::numberToString(m_iterator.currentSegmentOffset()) + " expected.", context);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void OggContainer::internalParseTags()
|
||||
{
|
||||
parseTracks(); // tracks needs to be parsed because tags are stored at stream level
|
||||
for(VorbisCommentInfo &vorbisCommentInfo : m_commentTable) {
|
||||
m_iterator.setPageIndex(vorbisCommentInfo.firstPageIndex);
|
||||
m_iterator.setSegmentIndex(vorbisCommentInfo.firstSegmentIndex);
|
||||
switch(vorbisCommentInfo.streamFormat) {
|
||||
// tracks needs to be parsed before because tags are stored at stream level
|
||||
parseTracks();
|
||||
for(auto &comment : m_tags) {
|
||||
OggParameter ¶ms = comment->oggParams();
|
||||
m_iterator.setPageIndex(params.firstPageIndex);
|
||||
m_iterator.setSegmentIndex(params.firstSegmentIndex);
|
||||
switch(params.streamFormat) {
|
||||
case GeneralMediaFormat::Vorbis:
|
||||
m_tags[vorbisCommentInfo.tagIndex]->parse(m_iterator);
|
||||
comment->parse(m_iterator);
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
m_iterator.seekForward(8); // skip header (has already been detected by OggStream)
|
||||
m_tags[vorbisCommentInfo.tagIndex]->parse(m_iterator, true);
|
||||
// skip header (has already been detected by OggStream)
|
||||
m_iterator.seekForward(8);
|
||||
comment->parse(m_iterator, true);
|
||||
break;
|
||||
default:
|
||||
addNotification(NotificationType::Critical, "Stream format not supported.", "parsing tags from OGG streams");
|
||||
}
|
||||
vorbisCommentInfo.lastPageIndex = m_iterator.currentPageIndex();
|
||||
vorbisCommentInfo.lastSegmentIndex = m_iterator.currentSegmentIndex();
|
||||
params.lastPageIndex = m_iterator.currentPageIndex();
|
||||
params.lastSegmentIndex = m_iterator.currentSegmentIndex();
|
||||
}
|
||||
}
|
||||
|
||||
void OggContainer::ariseComment(vector<OggPage>::size_type pageIndex, vector<uint32>::size_type segmentIndex, GeneralMediaFormat mediaFormat)
|
||||
void OggContainer::ariseComment(std::size_t pageIndex, std::size_t segmentIndex, GeneralMediaFormat mediaFormat)
|
||||
{
|
||||
m_commentTable.emplace_back(pageIndex, segmentIndex, m_tags.size(), mediaFormat);
|
||||
m_tags.emplace_back(make_unique<VorbisComment>());
|
||||
m_tags.back()->oggParams().set(pageIndex, segmentIndex, mediaFormat);
|
||||
}
|
||||
|
||||
void OggContainer::internalParseTracks()
|
||||
{
|
||||
if(!areTracksParsed()) {
|
||||
parseHeader();
|
||||
static const string context("parsing OGG stream");
|
||||
for(auto &stream : m_tracks) {
|
||||
try { // try to parse header
|
||||
stream->parseHeader();
|
||||
if(stream->duration() > m_duration) {
|
||||
m_duration = stream->duration();
|
||||
}
|
||||
} catch(const Failure &) {
|
||||
addNotification(NotificationType::Critical, "Unable to parse stream at " + ConversionUtilities::numberToString(stream->startOffset()) + ".", context);
|
||||
static const string context("parsing OGG stream");
|
||||
for(auto &stream : m_tracks) {
|
||||
try { // try to parse header
|
||||
stream->parseHeader();
|
||||
if(stream->duration() > m_duration) {
|
||||
m_duration = stream->duration();
|
||||
}
|
||||
} catch(const Failure &) {
|
||||
addNotification(NotificationType::Critical, "Unable to parse stream at " + ConversionUtilities::numberToString(stream->startOffset()) + ".", context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \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)
|
||||
{
|
||||
auto offset = buffer.tellp();
|
||||
switch(params->streamFormat) {
|
||||
case GeneralMediaFormat::Vorbis:
|
||||
comment->make(buffer);
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
ConversionUtilities::BE::getBytes(0x4F70757354616773u, copyHelper.buffer());
|
||||
buffer.write(copyHelper.buffer(), 8);
|
||||
comment->make(buffer, true);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
newSegmentSizes.push_back(buffer.tellp() - offset);
|
||||
}
|
||||
|
||||
void OggContainer::internalMakeFile()
|
||||
{
|
||||
const string context("making OGG file");
|
||||
|
@ -134,18 +270,32 @@ void OggContainer::internalMakeFile()
|
|||
BackupHelper::createBackupFile(fileInfo().path(), backupPath, backupStream);
|
||||
// recreate original file
|
||||
fileInfo().stream().open(fileInfo().path(), ios_base::out | ios_base::binary | ios_base::trunc);
|
||||
CopyHelper<65307> copy;
|
||||
auto commentTableIterator = m_commentTable.cbegin(), commentTableEnd = m_commentTable.cend();
|
||||
|
||||
// prepare iterating comments
|
||||
VorbisComment *currentComment;
|
||||
OggParameter *currentParams;
|
||||
auto tagIterator = m_tags.cbegin(), tagEnd = m_tags.cend();
|
||||
if(tagIterator != tagEnd) {
|
||||
currentParams = &(currentComment = tagIterator->get())->oggParams();
|
||||
} else {
|
||||
currentComment = nullptr;
|
||||
currentParams = nullptr;
|
||||
}
|
||||
|
||||
// define misc variables
|
||||
CopyHelper<65307> copyHelper;
|
||||
vector<uint64> updatedPageOffsets;
|
||||
uint32 pageSequenceNumber = 0;
|
||||
unordered_map<uint32, uint32> pageSequenceNumberBySerialNo;
|
||||
|
||||
// iterate through all pages of the original file
|
||||
for(m_iterator.setStream(backupStream), m_iterator.removeFilter(), m_iterator.reset(); m_iterator; m_iterator.nextPage()) {
|
||||
const auto ¤tPage = m_iterator.currentPage();
|
||||
auto pageSize = currentPage.totalSize();
|
||||
const OggPage ¤tPage = m_iterator.currentPage();
|
||||
const auto pageSize = currentPage.totalSize();
|
||||
uint32 &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.streamSerialNumber()];
|
||||
// check whether the Vorbis Comment is present in this Ogg page
|
||||
// -> then the page needs to be rewritten
|
||||
if(commentTableIterator != commentTableEnd
|
||||
&& m_iterator.currentPageIndex() >= commentTableIterator->firstPageIndex
|
||||
&& m_iterator.currentPageIndex() <= commentTableIterator->lastPageIndex
|
||||
if(currentComment
|
||||
&& m_iterator.currentPageIndex() >= currentParams->firstPageIndex
|
||||
&& m_iterator.currentPageIndex() <= currentParams->lastPageIndex
|
||||
&& !currentPage.segmentSizes().empty()) {
|
||||
// page needs to be rewritten (not just copied)
|
||||
// -> write segments to a buffer first
|
||||
|
@ -157,40 +307,48 @@ void OggContainer::internalMakeFile()
|
|||
for(const auto segmentSize : currentPage.segmentSizes()) {
|
||||
if(segmentSize) {
|
||||
// check whether this segment contains the Vorbis Comment
|
||||
if((m_iterator.currentPageIndex() > commentTableIterator->firstPageIndex || segmentIndex >= commentTableIterator->firstSegmentIndex)
|
||||
&& (m_iterator.currentPageIndex() < commentTableIterator->lastPageIndex || segmentIndex <= commentTableIterator->lastSegmentIndex)) {
|
||||
// prevent making the comment twice if it spreads over multiple pages
|
||||
if(m_iterator.currentPageIndex() == commentTableIterator->firstPageIndex) {
|
||||
// make Vorbis Comment segment
|
||||
auto offset = buffer.tellp();
|
||||
switch(commentTableIterator->streamFormat) {
|
||||
case GeneralMediaFormat::Vorbis:
|
||||
m_tags[commentTableIterator->tagIndex]->make(buffer);
|
||||
break;
|
||||
case GeneralMediaFormat::Opus:
|
||||
ConversionUtilities::BE::getBytes(0x4F70757354616773u, copy.buffer());
|
||||
buffer.write(copy.buffer(), 8);
|
||||
m_tags[commentTableIterator->tagIndex]->make(buffer, true);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
newSegmentSizes.push_back(buffer.tellp() - offset);
|
||||
if((m_iterator.currentPageIndex() >= currentParams->firstPageIndex && segmentIndex >= currentParams->firstSegmentIndex)
|
||||
&& (m_iterator.currentPageIndex() <= currentParams->lastPageIndex && segmentIndex <= currentParams->lastSegmentIndex)) {
|
||||
// prevent making the comment twice if it spreads over multiple pages/segments
|
||||
if(!currentParams->removed
|
||||
&& ((m_iterator.currentPageIndex() == currentParams->firstPageIndex
|
||||
&& m_iterator.currentSegmentIndex() == currentParams->firstSegmentIndex))) {
|
||||
makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams);
|
||||
}
|
||||
if(m_iterator.currentPageIndex() > commentTableIterator->lastPageIndex
|
||||
|| (m_iterator.currentPageIndex() == commentTableIterator->lastPageIndex && segmentIndex > commentTableIterator->lastSegmentIndex)) {
|
||||
++commentTableIterator;
|
||||
|
||||
// proceed with next comment?
|
||||
if(m_iterator.currentPageIndex() > currentParams->lastPageIndex
|
||||
|| (m_iterator.currentPageIndex() == currentParams->lastPageIndex && segmentIndex > currentParams->lastSegmentIndex)) {
|
||||
if(++tagIterator != tagEnd) {
|
||||
currentParams = &(currentComment = tagIterator->get())->oggParams();
|
||||
} else {
|
||||
currentComment = nullptr;
|
||||
currentParams = nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// copy other segments unchanged
|
||||
backupStream.seekg(segmentOffset);
|
||||
copy.copy(backupStream, buffer, segmentSize);
|
||||
copyHelper.copy(backupStream, buffer, segmentSize);
|
||||
newSegmentSizes.push_back(segmentSize);
|
||||
|
||||
// check whether there is a new comment to be inserted into the current page
|
||||
if(m_iterator.currentPageIndex() == currentParams->lastPageIndex && currentParams->firstSegmentIndex == static_cast<size_t>(-1)) {
|
||||
makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams);
|
||||
// proceed with next comment
|
||||
if(++tagIterator != tagEnd) {
|
||||
currentParams = &(currentComment = tagIterator->get())->oggParams();
|
||||
} else {
|
||||
currentComment = nullptr;
|
||||
currentParams = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
segmentOffset += segmentSize;
|
||||
}
|
||||
++segmentIndex;
|
||||
}
|
||||
|
||||
// write buffered data to actual stream
|
||||
auto newSegmentSizesIterator = newSegmentSizes.cbegin(), newSegmentSizesEnd = newSegmentSizes.cend();
|
||||
bool continuePreviousSegment = false;
|
||||
|
@ -201,7 +359,7 @@ void OggContainer::internalMakeFile()
|
|||
// write header
|
||||
backupStream.seekg(currentPage.startOffset());
|
||||
updatedPageOffsets.push_back(stream().tellp()); // memorize offset to update checksum later
|
||||
copy.copy(backupStream, stream(), 27); // just copy header from original file
|
||||
copyHelper.copy(backupStream, stream(), 27); // just copy header from original file
|
||||
// set continue flag
|
||||
stream().seekp(-22, ios_base::cur);
|
||||
stream().put(currentPage.headerTypeFlag() & (continuePreviousSegment ? 0xFF : 0xFE));
|
||||
|
@ -214,21 +372,21 @@ void OggContainer::internalMakeFile()
|
|||
// write segment sizes as long as there are segment sizes to be written and
|
||||
// the max number of segment sizes (255) is not exceeded
|
||||
uint32 currentSize = 0;
|
||||
while(bytesLeft > 0 && segmentSizesWritten < 0xFF) {
|
||||
while(bytesLeft && segmentSizesWritten < 0xFF) {
|
||||
while(bytesLeft >= 0xFF && segmentSizesWritten < 0xFF) {
|
||||
stream().put(0xFF);
|
||||
currentSize += 0xFF;
|
||||
bytesLeft -= 0xFF;
|
||||
++segmentSizesWritten;
|
||||
}
|
||||
if(bytesLeft > 0 && segmentSizesWritten < 0xFF) {
|
||||
if(bytesLeft && segmentSizesWritten < 0xFF) {
|
||||
// bytes left is here < 0xFF
|
||||
stream().put(bytesLeft);
|
||||
currentSize += bytesLeft;
|
||||
bytesLeft = 0;
|
||||
++segmentSizesWritten;
|
||||
}
|
||||
if(bytesLeft == 0) {
|
||||
if(!bytesLeft) {
|
||||
// sizes for the segment have been written
|
||||
// -> continue with next segment
|
||||
if(++newSegmentSizesIterator != newSegmentSizesEnd) {
|
||||
|
@ -237,10 +395,12 @@ void OggContainer::internalMakeFile()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there are no bytes left in the current segment; remove continue flag
|
||||
if(bytesLeft == 0) {
|
||||
if(!bytesLeft) {
|
||||
continuePreviousSegment = false;
|
||||
}
|
||||
|
||||
// page is full or all segment data has been covered
|
||||
// -> write segment table size (segmentSizesWritten) and segment data
|
||||
// -> seek back and write updated page segment number
|
||||
|
@ -248,43 +408,51 @@ void OggContainer::internalMakeFile()
|
|||
stream().put(segmentSizesWritten);
|
||||
stream().seekp(segmentSizesWritten, ios_base::cur);
|
||||
// -> write actual page data
|
||||
copy.copy(buffer, stream(), currentSize);
|
||||
copyHelper.copy(buffer, stream(), currentSize);
|
||||
++pageSequenceNumber;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if(pageSequenceNumber != m_iterator.currentPageIndex()) {
|
||||
// just update page sequence number
|
||||
backupStream.seekg(currentPage.startOffset());
|
||||
updatedPageOffsets.push_back(stream().tellp()); // memorize offset to update checksum later
|
||||
copy.copy(backupStream, stream(), 27);
|
||||
copyHelper.copy(backupStream, stream(), 27);
|
||||
stream().seekp(-9, ios_base::cur);
|
||||
writer().writeUInt32LE(pageSequenceNumber);
|
||||
stream().seekp(5, ios_base::cur);
|
||||
copy.copy(backupStream, stream(), pageSize - 27);
|
||||
copyHelper.copy(backupStream, stream(), pageSize - 27);
|
||||
} else {
|
||||
// copy page unchanged
|
||||
backupStream.seekg(currentPage.startOffset());
|
||||
copy.copy(backupStream, stream(), pageSize);
|
||||
copyHelper.copy(backupStream, stream(), pageSize);
|
||||
}
|
||||
++pageSequenceNumber;
|
||||
}
|
||||
}
|
||||
|
||||
fileInfo().reportSizeChanged(stream().tellp());
|
||||
|
||||
// close backups stream; reopen new file as readable stream
|
||||
backupStream.close();
|
||||
fileInfo().close();
|
||||
fileInfo().open();
|
||||
fileInfo().stream().open(fileInfo().path(), ios_base::in | ios_base::out | ios_base::binary);
|
||||
|
||||
// update checksums of modified pages
|
||||
for(auto offset : updatedPageOffsets) {
|
||||
OggPage::updateChecksum(fileInfo().stream(), offset);
|
||||
}
|
||||
|
||||
// clear iterator
|
||||
m_iterator = OggIterator(fileInfo().stream(), startOffset(), fileInfo().size());
|
||||
m_iterator.clear(fileInfo().stream(), startOffset(), fileInfo().size());
|
||||
|
||||
} catch(const OperationAbortedException &) {
|
||||
addNotification(NotificationType::Information, "Rewriting file to apply new tag information has been aborted.", context);
|
||||
BackupHelper::restoreOriginalFileFromBackupFile(fileInfo().path(), backupPath, fileInfo().stream(), backupStream);
|
||||
m_iterator.setStream(fileInfo().stream());
|
||||
throw;
|
||||
|
||||
} catch(const ios_base::failure &ex) {
|
||||
addNotification(NotificationType::Critical, "IO error occured when rewriting file to apply new tag information.\n" + string(ex.what()), context);
|
||||
BackupHelper::restoreOriginalFileFromBackupFile(fileInfo().path(), backupPath, fileInfo().stream(), backupStream);
|
||||
|
|
|
@ -16,27 +16,6 @@ namespace Media {
|
|||
|
||||
class MediaFileInfo;
|
||||
|
||||
struct LIB_EXPORT VorbisCommentInfo
|
||||
{
|
||||
VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex, GeneralMediaFormat streamFormat = GeneralMediaFormat::Vorbis);
|
||||
|
||||
std::vector<OggPage>::size_type firstPageIndex;
|
||||
std::vector<OggPage>::size_type firstSegmentIndex;
|
||||
std::vector<OggPage>::size_type lastPageIndex;
|
||||
std::vector<OggPage>::size_type lastSegmentIndex;
|
||||
std::vector<std::unique_ptr<VorbisComment> >::size_type tagIndex;
|
||||
GeneralMediaFormat streamFormat;
|
||||
};
|
||||
|
||||
inline VorbisCommentInfo::VorbisCommentInfo(std::vector<OggPage>::size_type firstPageIndex, std::vector<OggPage>::size_type firstSegmentIndex, std::vector<OggPage>::size_type tagIndex, GeneralMediaFormat mediaFormat) :
|
||||
firstPageIndex(firstPageIndex),
|
||||
firstSegmentIndex(firstSegmentIndex),
|
||||
lastPageIndex(0),
|
||||
lastSegmentIndex(0),
|
||||
tagIndex(tagIndex),
|
||||
streamFormat(mediaFormat)
|
||||
{}
|
||||
|
||||
class LIB_EXPORT OggContainer : public GenericContainer<MediaFileInfo, VorbisComment, OggStream, OggPage>
|
||||
{
|
||||
friend class OggStream;
|
||||
|
@ -49,6 +28,12 @@ public:
|
|||
void setChecksumValidationEnabled(bool enabled);
|
||||
void reset();
|
||||
|
||||
VorbisComment *createTag(const TagTarget &target);
|
||||
VorbisComment *tag(std::size_t index);
|
||||
std::size_t tagCount() const;
|
||||
bool removeTag(Tag *tag);
|
||||
void removeAllTags();
|
||||
|
||||
protected:
|
||||
void internalParseHeader();
|
||||
void internalParseTags();
|
||||
|
@ -56,15 +41,10 @@ protected:
|
|||
void internalMakeFile();
|
||||
|
||||
private:
|
||||
void ariseComment(std::vector<OggPage>::size_type pageIndex, std::vector<uint32>::size_type segmentIndex, GeneralMediaFormat mediaFormat = GeneralMediaFormat::Vorbis);
|
||||
void ariseComment(std::size_t pageIndex, std::size_t segmentIndex, GeneralMediaFormat mediaFormat = GeneralMediaFormat::Vorbis);
|
||||
|
||||
std::unordered_map<uint32, std::vector<std::unique_ptr<OggStream> >::size_type> m_streamsBySerialNo;
|
||||
|
||||
/*!
|
||||
* \brief Consists of first page index, first segment index, last page index, last segment index and tag index (in this order).
|
||||
*/
|
||||
std::list<VorbisCommentInfo> m_commentTable;
|
||||
|
||||
OggIterator m_iterator;
|
||||
bool m_validateChecksums;
|
||||
};
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
#include "../exceptions.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Media {
|
||||
|
||||
/*!
|
||||
|
@ -17,10 +21,23 @@ namespace Media {
|
|||
* The internal buffer of OGG pages might be accessed using the pages() method.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \brief Sets the stream and related parameters and clears all available pages.
|
||||
* \remarks Invalidates the iterator. Use reset() to continue iteration.
|
||||
*/
|
||||
void OggIterator::clear(istream &stream, uint64 startOffset, uint64 streamSize)
|
||||
{
|
||||
m_stream = &stream;
|
||||
m_startOffset = startOffset;
|
||||
m_streamSize = streamSize;
|
||||
m_pages.clear();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Resets the iterator to point at the first segment of the first page (matching the filter if set).
|
||||
*
|
||||
* Fetched pages (directly accessable through the page() method) remain after resetting the iterator.
|
||||
* Fetched pages (directly accessable through the page() method) remain after resetting the iterator. Use
|
||||
* the clear method to clear all pages.
|
||||
*/
|
||||
void OggIterator::reset()
|
||||
{
|
||||
|
@ -36,70 +53,66 @@ void OggIterator::reset()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Increases the current position by one page if the iterator is valid; does nothing otherwise.
|
||||
* \brief Increases the current position by one page.
|
||||
* \remarks The iterator must be valid. The iterator might be invalidated.
|
||||
*/
|
||||
void OggIterator::nextPage()
|
||||
{
|
||||
if(*this) {
|
||||
while(++m_page < m_pages.size() || fetchNextPage()) {
|
||||
const OggPage &page = m_pages[m_page];
|
||||
if(!page.segmentSizes().empty() && matchesFilter(page)) {
|
||||
// page is not empty and matches ID filter if set
|
||||
m_segment = m_bytesRead = 0;
|
||||
m_offset = page.startOffset() + page.headerSize();
|
||||
return;
|
||||
}
|
||||
while(++m_page < m_pages.size() || fetchNextPage()) {
|
||||
const OggPage &page = m_pages[m_page];
|
||||
if(!page.segmentSizes().empty() && matchesFilter(page)) {
|
||||
// page is not empty and matches ID filter if set
|
||||
m_segment = m_bytesRead = 0;
|
||||
m_offset = page.startOffset() + page.headerSize();
|
||||
return;
|
||||
}
|
||||
// no next page available -> iterator is in invalid state
|
||||
}
|
||||
// no next page available -> iterator is in invalid state
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Increases the current position by one segment if the iterator is valid; does nothing otherwise.
|
||||
* \brief Increases the current position by one segment.
|
||||
* \remarks The iterator must be valid. The iterator might be invalidated.
|
||||
*/
|
||||
void OggIterator::nextSegment()
|
||||
{
|
||||
if(*this) {
|
||||
const OggPage &page = m_pages[m_page];
|
||||
if(m_segment + 1 < page.segmentSizes().size() && matchesFilter(page)) {
|
||||
// current page has next segment
|
||||
m_bytesRead = 0;
|
||||
m_offset += page.segmentSizes()[m_segment++];
|
||||
} else {
|
||||
// next (matching) page has next segment
|
||||
nextPage();
|
||||
}
|
||||
const OggPage &page = m_pages[m_page];
|
||||
if(matchesFilter(page) && ++m_segment < page.segmentSizes().size()) {
|
||||
// current page has next segment
|
||||
m_bytesRead = 0;
|
||||
m_offset += page.segmentSizes()[m_segment - 1];
|
||||
} else {
|
||||
// next (matching) page has next segment
|
||||
nextPage();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Decreases the current position by one page if the iterator is valid; does nothing otherwise.
|
||||
* \brief Decreases the current position by one page.
|
||||
* \remarks The iterator must be valid. The iterator might be invalidated.
|
||||
*/
|
||||
void OggIterator::previousPage()
|
||||
{
|
||||
if(*this) {
|
||||
while(m_page > 0) {
|
||||
const OggPage &page = m_pages[--m_page];
|
||||
if(matchesFilter(page)) {
|
||||
m_offset = page.dataOffset(m_segment = page.segmentSizes().size() - 1);
|
||||
return;
|
||||
}
|
||||
while(m_page) {
|
||||
const OggPage &page = m_pages[--m_page];
|
||||
if(matchesFilter(page)) {
|
||||
m_offset = page.dataOffset(m_segment = page.segmentSizes().size() - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Decreases the current position by one segment if the iterator is valid; does nothing otherwise.
|
||||
* \brief Decreases the current position by one segment.
|
||||
* \remarks The iterator must be valid. The iterator might be invalidated.
|
||||
*/
|
||||
void OggIterator::previousSegment()
|
||||
{
|
||||
if(*this) {
|
||||
const OggPage &page = m_pages[m_page];
|
||||
if(m_segment > 0 && matchesFilter(page)) {
|
||||
m_offset -= page.segmentSizes()[m_segment--];
|
||||
} else {
|
||||
previousPage();
|
||||
}
|
||||
const OggPage &page = m_pages[m_page];
|
||||
if(m_segment && matchesFilter(page)) {
|
||||
m_offset -= page.segmentSizes()[m_segment--];
|
||||
} else {
|
||||
previousPage();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,7 +143,10 @@ void OggIterator::read(char *buffer, size_t count)
|
|||
count -= available;
|
||||
}
|
||||
}
|
||||
throw TruncatedDataException();
|
||||
if(count) {
|
||||
// still bytes to read but no more available
|
||||
throw TruncatedDataException();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -173,9 +189,7 @@ bool OggIterator::fetchNextPage()
|
|||
if(m_page == m_pages.size()) { // can only fetch the next page if the current page is the last page
|
||||
m_offset = m_pages.empty() ? m_startOffset : m_pages.back().startOffset() + m_pages.back().totalSize();
|
||||
if(m_offset < m_streamSize) {
|
||||
OggPage page;
|
||||
page.parseHeader(*m_stream, m_offset, static_cast<int32>(m_streamSize - m_offset));
|
||||
m_pages.push_back(page);
|
||||
m_pages.emplace_back(*m_stream, m_offset, static_cast<int32>(m_streamSize - m_offset));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ class LIB_EXPORT OggIterator
|
|||
public:
|
||||
OggIterator(std::istream &stream, uint64 startOffset, uint64 streamSize);
|
||||
|
||||
void clear(std::istream &stream, uint64 startOffset, uint64 streamSize);
|
||||
std::istream &stream();
|
||||
void setStream(std::istream &stream);
|
||||
uint64 startOffset() const;
|
||||
|
|
|
@ -77,7 +77,7 @@ uint32 OggPage::computeChecksum(istream &stream, uint64 startOffset)
|
|||
stream.seekg(startOffset);
|
||||
uint32 crc = 0x0;
|
||||
byte value, segmentTableSize = 0, segmentTableIndex = 0;
|
||||
for(uint32 i = 0, segmentLength = 27; i < segmentLength; ++i) {
|
||||
for(uint32 i = 0, segmentLength = 27; i != segmentLength; ++i) {
|
||||
switch(i) {
|
||||
case 22:
|
||||
// bytes 22, 23, 24, 25 hold denoted checksum and must be set to zero
|
||||
|
|
|
@ -14,6 +14,7 @@ class LIB_EXPORT OggPage
|
|||
{
|
||||
public:
|
||||
OggPage();
|
||||
OggPage(std::istream &stream, uint64 startOffset, int32 maxSize);
|
||||
|
||||
void parseHeader(std::istream &stream, uint64 startOffset, int32 maxSize);
|
||||
static uint32 computeChecksum(std::istream &stream, uint64 startOffset);
|
||||
|
@ -62,6 +63,16 @@ inline OggPage::OggPage() :
|
|||
m_segmentCount(0)
|
||||
{}
|
||||
|
||||
/*!
|
||||
* \brief Constructs a new OggPage and instantly parses the header read from the specified \a stream
|
||||
* at the specified \a startOffset.
|
||||
*/
|
||||
inline OggPage::OggPage(std::istream &stream, uint64 startOffset, int32 maxSize) :
|
||||
OggPage()
|
||||
{
|
||||
parseHeader(stream, startOffset, maxSize);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the start offset of the page.
|
||||
*
|
||||
|
|
|
@ -43,24 +43,29 @@ OggStream::~OggStream()
|
|||
void OggStream::internalParseHeader()
|
||||
{
|
||||
static const string context("parsing OGG page header");
|
||||
|
||||
// read basic information from first page
|
||||
OggIterator &iterator = m_container.m_iterator;
|
||||
const OggPage &firstPage = iterator.pages()[m_startPage];
|
||||
m_version = firstPage.streamStructureVersion();
|
||||
m_id = firstPage.streamSerialNumber();
|
||||
|
||||
// ensure iterator is setup properly
|
||||
iterator.setFilter(m_id);
|
||||
iterator.setPageIndex(m_startPage);
|
||||
|
||||
// iterate through segments using OggIterator
|
||||
bool hasIdentificationHeader = false;
|
||||
bool hasCommentHeader = false;
|
||||
for(; iterator; ++iterator) {
|
||||
const uint32 currentSize = iterator.currentSegmentSize();
|
||||
m_size += currentSize;
|
||||
|
||||
if(currentSize >= 8) {
|
||||
// determine stream format
|
||||
inputStream().seekg(iterator.currentSegmentOffset());
|
||||
const uint64 sig = reader().readUInt64BE();
|
||||
|
||||
if((sig & 0x00ffffffffffff00u) == 0x00766F7262697300u) {
|
||||
// Vorbis header detected
|
||||
// set Vorbis as format
|
||||
|
@ -124,6 +129,7 @@ void OggStream::internalParseHeader()
|
|||
default:
|
||||
;
|
||||
}
|
||||
|
||||
} else if(sig == 0x4F70757348656164u) {
|
||||
// Opus header detected
|
||||
// set Opus as format
|
||||
|
@ -167,6 +173,7 @@ void OggStream::internalParseHeader()
|
|||
} else {
|
||||
addNotification(NotificationType::Critical, "Opus identification header appears more then once. Oversupplied occurrence will be ignored.", context);
|
||||
}
|
||||
|
||||
} else if(sig == 0x4F70757354616773u) {
|
||||
// Opus comment detected
|
||||
// set Opus as format
|
||||
|
@ -187,6 +194,7 @@ void OggStream::internalParseHeader()
|
|||
} else {
|
||||
addNotification(NotificationType::Critical, "Opus tags/comment header appears more then once. Oversupplied occurrence will be ignored.", context);
|
||||
}
|
||||
|
||||
} else if((sig & 0x00ffffffffffff00u) == 0x007468656F726100u) {
|
||||
// Theora header detected
|
||||
// set Theora as format
|
||||
|
@ -201,9 +209,10 @@ void OggStream::internalParseHeader()
|
|||
addNotification(NotificationType::Warning, "Stream format is inconsistent.", context);
|
||||
}
|
||||
// TODO: read more information about Theora stream
|
||||
} // currently only Vorbis, Opus and Theora can be detected
|
||||
}
|
||||
} // currently only Vorbis, Opus and Theora can be detected, TODO: detect more formats
|
||||
} // TODO: reduce code duplication
|
||||
}
|
||||
|
||||
if(m_duration.isNull() && m_size && m_bitrate) {
|
||||
// calculate duration from stream size and bitrate, assuming 1 % overhead
|
||||
m_duration = TimeSpan::fromSeconds(static_cast<double>(m_size) / (m_bitrate * 125.0) * 1.1);
|
||||
|
|
|
@ -19,16 +19,22 @@ public:
|
|||
~OggStream();
|
||||
|
||||
TrackType type() const;
|
||||
std::size_t startPage() const;
|
||||
|
||||
protected:
|
||||
void internalParseHeader();
|
||||
|
||||
private:
|
||||
std::vector<OggPage>::size_type m_startPage;
|
||||
std::size_t m_startPage;
|
||||
OggContainer &m_container;
|
||||
uint32 m_currentSequenceNumber;
|
||||
};
|
||||
|
||||
inline std::size_t OggStream::startPage() const
|
||||
{
|
||||
return m_startPage;
|
||||
}
|
||||
|
||||
inline TrackType OggStream::type() const
|
||||
{
|
||||
return TrackType::OggStream;
|
||||
|
|
|
@ -145,7 +145,6 @@ SOURCES += \
|
|||
vorbis/vorbisidentificationheader.cpp \
|
||||
opus/opusidentificationheader.cpp \
|
||||
ogg/oggiterator.cpp \
|
||||
vorbis/vorbiscommentids.cpp \
|
||||
abstractchapter.cpp \
|
||||
matroska/matroskaeditionentry.cpp \
|
||||
matroska/matroskachapter.cpp \
|
||||
|
|
55
tagvalue.cpp
55
tagvalue.cpp
|
@ -164,35 +164,46 @@ TagValue &TagValue::operator=(const TagValue &other)
|
|||
*/
|
||||
bool TagValue::operator==(const TagValue &other) const
|
||||
{
|
||||
if(m_type != other.m_type || m_desc != other.m_desc || (!m_desc.empty() && m_descEncoding != other.m_descEncoding)
|
||||
if(m_desc != other.m_desc || (!m_desc.empty() && m_descEncoding != other.m_descEncoding)
|
||||
|| m_mimeType != other.m_mimeType || m_lng != other.m_lng || m_labeledAsReadonly != other.m_labeledAsReadonly) {
|
||||
return false;
|
||||
}
|
||||
switch(m_type) {
|
||||
case TagDataType::Text:
|
||||
if(m_size != other.m_size && m_encoding != other.m_encoding) {
|
||||
if(m_type == other.m_type) {
|
||||
switch(m_type) {
|
||||
case TagDataType::Text:
|
||||
if(m_size != other.m_size && m_encoding != other.m_encoding) {
|
||||
return false;
|
||||
}
|
||||
return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
|
||||
case TagDataType::PositionInSet:
|
||||
return toPositionInSet() == other.toPositionInSet();
|
||||
case TagDataType::Integer:
|
||||
return toInteger() == other.toInteger();
|
||||
case TagDataType::StandardGenreIndex:
|
||||
return toStandardGenreIndex() == other.toStandardGenreIndex();
|
||||
case TagDataType::TimeSpan:
|
||||
return toTimeSpan() == other.toTimeSpan();
|
||||
case TagDataType::DateTime:
|
||||
return toDateTime() == other.toDateTime();
|
||||
case TagDataType::Picture:
|
||||
case TagDataType::Binary:
|
||||
case TagDataType::Undefined:
|
||||
if(m_size != other.m_size) {
|
||||
return false;
|
||||
}
|
||||
return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
|
||||
case TagDataType::PositionInSet:
|
||||
return toPositionInSet() == other.toPositionInSet();
|
||||
case TagDataType::Integer:
|
||||
return toInteger() == other.toInteger();
|
||||
case TagDataType::StandardGenreIndex:
|
||||
return toStandardGenreIndex() == other.toStandardGenreIndex();
|
||||
case TagDataType::TimeSpan:
|
||||
return toTimeSpan() == other.toTimeSpan();
|
||||
case TagDataType::DateTime:
|
||||
return toDateTime() == other.toDateTime();
|
||||
case TagDataType::Picture:
|
||||
case TagDataType::Binary:
|
||||
case TagDataType::Undefined:
|
||||
if(m_size != other.m_size) {
|
||||
} else {
|
||||
// different types
|
||||
try {
|
||||
// try to convert both values to string
|
||||
// if the string representations are equal, both values can also be considered equal
|
||||
return toString() == other.toString();
|
||||
} catch(const ConversionException &) {
|
||||
return false;
|
||||
}
|
||||
return strncmp(m_ptr.get(), other.m_ptr.get(), m_size) == 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ string VorbisComment::fieldId(KnownField field) const
|
|||
case KnownField::DiskPosition: return diskNumber();
|
||||
case KnownField::PartNumber: return partNumber();
|
||||
case KnownField::Composer: return composer();
|
||||
case KnownField::Encoder: return encodedBy();
|
||||
case KnownField::Encoder: return encoder();
|
||||
case KnownField::EncoderSettings: return encoderSettings();
|
||||
case KnownField::Description: return description();
|
||||
case KnownField::RecordLabel: return label();
|
||||
|
@ -73,7 +73,7 @@ string VorbisComment::fieldId(KnownField field) const
|
|||
KnownField VorbisComment::knownField(const string &id) const
|
||||
{
|
||||
using namespace VorbisCommentIds;
|
||||
static const map<string, KnownField> map({
|
||||
static const map<string, KnownField> fieldMap({
|
||||
{album(), KnownField::Album},
|
||||
{artist(), KnownField::Artist},
|
||||
{comment(), KnownField::Comment},
|
||||
|
@ -85,7 +85,7 @@ KnownField VorbisComment::knownField(const string &id) const
|
|||
{diskNumber(), KnownField::DiskPosition},
|
||||
{partNumber(), KnownField::PartNumber},
|
||||
{composer(), KnownField::Composer},
|
||||
{encodedBy(), KnownField::Encoder},
|
||||
{encoder(), KnownField::Encoder},
|
||||
{encoderSettings(), KnownField::EncoderSettings},
|
||||
{description(), KnownField::Description},
|
||||
{label(), KnownField::RecordLabel},
|
||||
|
@ -93,7 +93,7 @@ KnownField VorbisComment::knownField(const string &id) const
|
|||
{lyricist(), KnownField::Lyricist}
|
||||
});
|
||||
try {
|
||||
return map.at(id);
|
||||
return fieldMap.at(id);
|
||||
} catch(out_of_range &) {
|
||||
return KnownField::Invalid;
|
||||
}
|
||||
|
@ -181,7 +181,7 @@ void VorbisComment::make(std::ostream &stream, bool noSignature)
|
|||
string vendor;
|
||||
try {
|
||||
m_vendor.toString(vendor);
|
||||
} catch(ConversionException &) {
|
||||
} catch(const ConversionException &) {
|
||||
addNotification(NotificationType::Warning, "Can not convert the assigned vendor to string.", context);
|
||||
}
|
||||
BinaryWriter writer(&stream);
|
||||
|
|
|
@ -5,13 +5,56 @@
|
|||
|
||||
#include "../caseinsensitivecomparer.h"
|
||||
#include "../fieldbasedtag.h"
|
||||
#include "../mediaformat.h"
|
||||
|
||||
namespace Media {
|
||||
|
||||
class OggIterator;
|
||||
class VorbisComment;
|
||||
|
||||
/*!
|
||||
* \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, GeneralMediaFormat streamFormat = GeneralMediaFormat::Vorbis);
|
||||
|
||||
std::size_t firstPageIndex;
|
||||
std::size_t firstSegmentIndex;
|
||||
std::size_t lastPageIndex;
|
||||
std::size_t lastSegmentIndex;
|
||||
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),
|
||||
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)
|
||||
{
|
||||
firstPageIndex = lastPageIndex = pageIndex;
|
||||
firstSegmentIndex = lastSegmentIndex = segmentIndex;
|
||||
this->streamFormat = streamFormat;
|
||||
}
|
||||
|
||||
class LIB_EXPORT VorbisComment : public FieldMapBasedTag<VorbisCommentField, CaseInsensitiveStringComparer>
|
||||
{
|
||||
friend class OggContainer;
|
||||
|
||||
public:
|
||||
VorbisComment();
|
||||
|
||||
|
@ -30,9 +73,12 @@ public:
|
|||
|
||||
const TagValue &vendor() const;
|
||||
void setVendor(const TagValue &vendor);
|
||||
OggParameter &oggParams();
|
||||
const OggParameter &oggParams() const;
|
||||
|
||||
private:
|
||||
TagValue m_vendor;
|
||||
OggParameter m_oggParams;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -48,7 +94,15 @@ inline TagType VorbisComment::type() const
|
|||
|
||||
inline const char *VorbisComment::typeName() const
|
||||
{
|
||||
return "Vorbis comment";
|
||||
switch(m_oggParams.streamFormat) {
|
||||
case GeneralMediaFormat::Opus:
|
||||
return "Opus comment";
|
||||
case GeneralMediaFormat::Theora:
|
||||
return "Theora comment";
|
||||
default:
|
||||
// just assume Vorbis otherwise
|
||||
return "Vorbis comment";
|
||||
}
|
||||
}
|
||||
|
||||
inline TagTextEncoding VorbisComment::proposedTextEncoding() const
|
||||
|
@ -61,16 +115,46 @@ inline bool VorbisComment::canEncodingBeUsed(TagTextEncoding encoding) const
|
|||
return encoding == TagTextEncoding::Utf8;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the vendor.
|
||||
* \remarks Also accessable via value(KnownField::Vendor).
|
||||
*/
|
||||
inline const TagValue &VorbisComment::vendor() const
|
||||
{
|
||||
return m_vendor;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the vendor.
|
||||
* \remarks Also accessable via setValue(KnownField::Vendor, vendor).
|
||||
*/
|
||||
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,9 +0,0 @@
|
|||
#include "./vorbiscommentids.h"
|
||||
|
||||
namespace Media {
|
||||
|
||||
namespace VorbisCommentIds {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -64,8 +64,8 @@ inline LIB_EXPORT const char *author() {
|
|||
inline LIB_EXPORT const char *conductor() {
|
||||
return "CONDUCTOR";
|
||||
}
|
||||
inline LIB_EXPORT const char *encodedBy() {
|
||||
return "ENCODED-BY";
|
||||
inline LIB_EXPORT const char *encoder() {
|
||||
return "ENCODER";
|
||||
}
|
||||
inline LIB_EXPORT const char *publisher() {
|
||||
return "PUBLISHER";
|
||||
|
|
Loading…
Reference in New Issue