3 #include "../flac/flacmetadata.h" 5 #include "../backuphelper.h" 6 #include "../mediafileinfo.h" 7 #include "../progressfeedback.h" 9 #include <c++utilities/conversion/stringbuilder.h> 10 #include <c++utilities/io/catchiofailure.h> 11 #include <c++utilities/io/copy.h> 26 const char *OggVorbisComment::typeName()
const 28 switch (m_oggParams.streamFormat) {
30 return "Vorbis comment (in FLAC stream)";
31 case GeneralMediaFormat::Opus:
32 return "Vorbis comment (in Opus stream)";
33 case GeneralMediaFormat::Theora:
34 return "Vorbis comment (in Theora stream)";
36 return "Vorbis comment";
50 , m_iterator(fileInfo.stream(), startOffset, fileInfo.size())
51 , m_validateChecksums(false)
76 if (!target.
tracks().empty()) {
93 }
else if (!
m_tags.empty()) {
95 m_tags.front()->oggParams().removed =
false;
96 return m_tags.front().get();
110 m_tags.back()->setTarget(target);
111 return m_tags.back().get();
159 for (
auto &existingTag :
m_tags) {
160 if (static_cast<Tag *>(existingTag.get()) ==
tag) {
161 existingTag->removeAllFields();
162 existingTag->oggParams().removed =
true;
181 for (
auto &existingTag :
m_tags) {
182 existingTag->removeAllFields();
183 existingTag->oggParams().removed =
true;
189 static const string context(
"parsing OGG bitstream header");
190 bool pagesSkipped =
false;
200 "The denoted checksum of the OGG page at ", m_iterator.
currentSegmentOffset(),
" does not match the computed checksum."),
204 uint64 lastNewStreamOffset = 0;
208 }
catch (
const out_of_range &) {
216 if (
stream->m_currentSequenceNumber) {
217 diag.emplace_back(
DiagLevel::Warning,
"Page is missing (page sequence number omitted).", context);
221 ++
stream->m_currentSequenceNumber;
226 && (page.
startOffset() - lastNewStreamOffset) > (20 * 0x100000)) {
233 argsToString(
"Pages in the middle of the file (", dataSizeToString(resyncedPage.
startOffset() - page.
startOffset()),
234 ") have been skipped to improve parsing speed. Hence track sizes can not be computed. Maybe not even all tracks could be " 235 "detected. Force a full parse to prevent this."),
240 "Unable to re-sync after skipping OGG pages in the middle of the file. Try forcing a full parse.", context);
284 diag.emplace_back(
DiagLevel::Critical,
"Stream format not supported.",
"parsing tags from OGG streams");
302 void OggContainer::announceComment(std::size_t pageIndex, std::size_t segmentIndex,
bool lastMetaDataBlock,
GeneralMediaFormat mediaFormat)
304 m_tags.emplace_back(make_unique<OggVorbisComment>());
305 m_tags.back()->oggParams().set(pageIndex, segmentIndex, lastMetaDataBlock, mediaFormat);
310 static const string context(
"parsing OGG stream");
313 stream->parseHeader(diag);
327 void OggContainer::makeVorbisCommentSegment(stringstream &buffer,
CopyHelper<65307> ©Helper, vector<uint32> &newSegmentSizes,
330 const auto offset = buffer.tellp();
336 ConversionUtilities::BE::getBytes(static_cast<uint64>(0x4F70757354616773u), copyHelper.buffer());
337 buffer.write(copyHelper.buffer(), 8);
347 buffer.write(copyHelper.buffer(), 4);
355 DiagLevel::Critical,
"Size of Vorbis comment exceeds size limit for FLAC \"METADATA_BLOCK_HEADER\".",
"making Vorbis Comment");
357 buffer.seekp(offset);
359 buffer.seekp(header.
dataSize(), ios_base::cur);
364 newSegmentSizes.push_back(buffer.tellp() - offset);
369 const string context(
"making OGG file");
370 progress.
updateStep(
"Prepare for rewriting OGG file ...");
373 NativeFileStream backupStream;
375 if (
fileInfo().saveFilePath().empty()) {
382 const char *what = catchIoFailure();
383 diag.emplace_back(
DiagLevel::Critical,
"Creation of temporary file (to rewrite the original file) failed.", context);
384 throwIoFailure(what);
389 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
390 backupStream.open(
fileInfo().path(), ios_base::in | ios_base::binary);
394 const char *what = catchIoFailure();
395 diag.emplace_back(
DiagLevel::Critical,
"Opening streams to write output file failed.", context);
396 throwIoFailure(what);
404 auto tagIterator =
m_tags.cbegin(), tagEnd =
m_tags.cend();
405 if (tagIterator != tagEnd) {
406 currentParams = &(currentComment = tagIterator->get())->oggParams();
408 currentComment =
nullptr;
409 currentParams =
nullptr;
414 vector<uint64> updatedPageOffsets;
415 unordered_map<uint32, uint32> pageSequenceNumberBySerialNo;
420 const auto pageSize = currentPage.
totalSize();
421 uint32 &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.
streamSerialNumber()];
427 stringstream buffer(ios_base::in | ios_base::out | ios_base::binary);
428 vector<uint32> newSegmentSizes;
429 newSegmentSizes.reserve(currentPage.
segmentSizes().size());
431 vector<uint32>::size_type segmentIndex = 0;
432 for (
const auto segmentSize : currentPage.
segmentSizes()) {
441 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
448 if (++tagIterator != tagEnd) {
449 currentParams = &(currentComment = tagIterator->get())->oggParams();
451 currentComment =
nullptr;
452 currentParams =
nullptr;
457 backupStream.seekg(segmentOffset);
458 copyHelper.copy(backupStream, buffer, segmentSize);
459 newSegmentSizes.push_back(segmentSize);
465 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
468 if (++tagIterator != tagEnd) {
469 currentParams = &(currentComment = tagIterator->get())->oggParams();
471 currentComment =
nullptr;
472 currentParams =
nullptr;
476 segmentOffset += segmentSize;
482 auto newSegmentSizesIterator = newSegmentSizes.cbegin(), newSegmentSizesEnd = newSegmentSizes.cend();
483 bool continuePreviousSegment =
false;
484 if (newSegmentSizesIterator != newSegmentSizesEnd) {
485 uint32 bytesLeft = *newSegmentSizesIterator;
487 while (newSegmentSizesIterator != newSegmentSizesEnd) {
490 updatedPageOffsets.push_back(
stream().tellp());
491 copyHelper.copy(backupStream,
stream(), 27);
493 stream().seekp(-22, ios_base::cur);
495 continuePreviousSegment =
true;
497 stream().seekp(12, ios_base::cur);
498 writer().writeUInt32LE(pageSequenceNumber);
499 stream().seekp(5, ios_base::cur);
500 int16 segmentSizesWritten = 0;
503 uint32 currentSize = 0;
504 while (bytesLeft && segmentSizesWritten < 0xFF) {
505 while (bytesLeft >= 0xFF && segmentSizesWritten < 0xFF) {
509 ++segmentSizesWritten;
511 if (bytesLeft && segmentSizesWritten < 0xFF) {
514 currentSize += bytesLeft;
516 ++segmentSizesWritten;
521 if (++newSegmentSizesIterator != newSegmentSizesEnd) {
522 bytesLeft = *newSegmentSizesIterator;
523 continuePreviousSegment =
false;
530 continuePreviousSegment =
false;
536 stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
537 stream().put(segmentSizesWritten);
538 stream().seekp(segmentSizesWritten, ios_base::cur);
540 copyHelper.copy(buffer,
stream(), currentSize);
542 ++pageSequenceNumber;
550 updatedPageOffsets.push_back(
stream().tellp());
551 copyHelper.copy(backupStream,
stream(), 27);
552 stream().seekp(-9, ios_base::cur);
553 writer().writeUInt32LE(pageSequenceNumber);
554 stream().seekp(5, ios_base::cur);
555 copyHelper.copy(backupStream,
stream(), pageSize - 27);
559 copyHelper.copy(backupStream,
stream(), pageSize);
561 ++pageSequenceNumber;
569 if (!
fileInfo().saveFilePath().empty()) {
575 backupStream.close();
580 for (
auto offset : updatedPageOffsets) {
void nextPage()
Increases the current position by one page.
byte headerTypeFlag() const
Returns the header type flag.
void reset()
Resets the iterator to point at the first segment of the first page (matching the filter if set)...
The OggParameter struct holds the OGG parameter for a VorbisComment.
uint32 dataSize() const
Returns the data size in byte.
uint32 totalSize() const
Returns the total size of the page in byte.
GeneralMediaFormat
The GeneralMediaFormat enum specifies the general format of media data (PCM, MPEG-4, PNG, ...).
const OggPage & currentPage() const
Returns the current OGG page.
void removeFilter()
Removes a previously set filter.
std::iostream & stream()
Returns the related stream.
OggVorbisComment * createTag(const TagTarget &target) override
Creates a new tag.
const std::vector< uint32 > & segmentSizes() const
Returns the sizes of the segments of the page in byte.
OggStream * track(std::size_t index) override
uint64 currentSegmentOffset() const
Returns the start offset of the current segment in the input stream if the iterator is valid; otherwi...
uint32 sequenceNumber() const
Returns the page sequence number.
void ignore(std::size_t count=1)
Advances the position of the next character to be read from the OGG stream by count bytes...
TAG_PARSER_EXPORT const char * comment()
void parseTracks(Diagnostics &diag)
Parses the tracks of the file if not parsed yet.
uint64 startOffset() const
Returns the start offset of the page.
std::size_t startPage() const
std::vector< uint32 >::size_type currentSegmentIndex() const
Returns the index of the current segment (in the current page) if the iterator is valid; otherwise an...
const TagTarget & target() const
Returns the target of tag.
std::size_t lastSegmentIndex
std::vector< OggPage >::size_type currentPageIndex() const
Returns the index of the current page if the iterator is valid; otherwise an undefined index is retur...
ChronoUtilities::TimeSpan m_duration
void removeAllTags() override
Actually just flags all tags as removed and clears all assigned fields.
uint32 checksum() const
Returns the page checksum.
const std::vector< OggPage > & pages() const
Returns a vector of containing the OGG pages that have been fetched yet.
bool resyncAt(uint64 offset)
Fetches the next page at the specified offset.
Contains utility classes helping to read and write streams.
void setStream(std::istream &stream)
Sets the stream.
uint32 streamSerialNumber() const
Returns the stream serial number.
std::size_t firstSegmentIndex
void setPageIndex(std::vector< OggPage >::size_type index)
Sets the current page index.
OggVorbisComment * tag(std::size_t index) override
Returns the tag with the specified index.
IoUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
bool removeTag(Tag *tag) override
Actually just flags the specified tag as removed and clears all assigned fields.
MediaFormat format() const
Returns the format of the track if known; otherwise returns MediaFormat::Unknown. ...
std::vector< std::unique_ptr< OggStream > > m_tracks
void reportPathChanged(const std::string &newPath)
Call this function to report that the path changed.
void close()
A possibly opened std::fstream will be closed.
void internalParseHeader(Diagnostics &diag) override
Internally called to parse the header.
uint64 id() const
Returns the track ID if known; otherwise returns 0.
void updateStep(const std::string &step, byte stepPercentage=0)
void internalParseTracks(Diagnostics &diag) override
Internally called to parse the tracks.
TAG_PARSER_EXPORT void handleFailureAfterFileModified(MediaFileInfo &mediaFileInfo, const std::string &backupPath, IoUtilities::NativeFileStream &outputStream, IoUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
void reportSizeChanged(uint64 newSize)
Call this function to report that the size changed.
const IdContainerType & tracks() const
Returns the tracks.
static void updateChecksum(std::iostream &stream, uint64 startOffset)
Updates the checksum of the page read from the specified stream at the specified startOffset.
std::size_t lastPageIndex
std::size_t tagCount() const override
Returns the number of tags attached to the container.
GeneralMediaFormat streamFormat
uint64 startOffset() const
Returns the start offset in the related stream.
IoUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
void setSegmentIndex(std::vector< uint32 >::size_type index)
Sets the current segment index.
void internalParseTags(Diagnostics &diag) override
Internally called to parse the tags.
MediaFileInfo & fileInfo() const
Returns the related file info.
TAG_PARSER_EXPORT void createBackupFile(const std::string &originalPath, std::string &backupPath, IoUtilities::NativeFileStream &originalStream, IoUtilities::NativeFileStream &backupStream)
static uint32 computeChecksum(std::istream &stream, uint64 startOffset)
Computes the actual checksum of the page read from the specified stream at the specified startOffset...
std::size_t firstPageIndex
std::vector< std::unique_ptr< OggVorbisComment > > m_tags
void parseTags(Diagnostics &diag)
Parses the tag information if not parsed yet.
void reset() override
Discards all parsing results.
void clear(std::istream &stream, uint64 startOffset, uint64 streamSize)
Sets the stream and related parameters and clears all available pages.
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.