10#include <c++utilities/conversion/stringbuilder.h>
11#include <c++utilities/io/copy.h>
30 return "Vorbis comment (in FLAC stream)";
32 return "Vorbis comment (in Opus stream)";
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();
126 auto i = std::size_t();
140 auto count = std::size_t();
160 for (
auto &existingTag :
m_tags) {
161 if (
static_cast<Tag *
>(existingTag.get()) ==
tag) {
162 existingTag->removeAllFields();
163 existingTag->oggParams().removed =
true;
182 for (
auto &existingTag :
m_tags) {
183 existingTag->removeAllFields();
184 existingTag->oggParams().removed =
true;
190 CPP_UTILITIES_UNUSED(progress)
192 static const auto context = std::string(
"parsing Ogg bitstream header");
193 auto pagesSkipped =
false, continueFromHere =
false;
199 continueFromHere ? [&] { continueFromHere = false; }() : m_iterator.
nextPage()) {
205 "The denoted checksum of the Ogg page at ", m_iterator.
currentSegmentOffset(),
" does not match the computed checksum."),
209 auto lastNewStreamOffset = std::uint64_t();
210 if (
const auto streamIndex = m_streamsBySerialNo.find(page.
streamSerialNumber()); streamIndex != m_streamsBySerialNo.end()) {
222 if (
stream->m_currentSequenceNumber) {
224 argsToString(
"Page of stream ", page.
streamSerialNumber(),
" missing; page sequence number ",
stream->m_currentSequenceNumber,
230 ++
stream->m_currentSequenceNumber;
235 && (page.
startOffset() - lastNewStreamOffset) > (20 * 0x100000)) {
239 for (
auto &trackStream :
m_tracks) {
240 trackStream->m_currentSequenceNumber = 0;
241 trackStream->m_size = 0;
243 pagesSkipped = continueFromHere =
true;
245 argsToString(
"Pages in the middle of the file (", dataSizeToString(resyncedPage.
startOffset() - page.
startOffset()),
246 ") have been skipped to improve parsing speed. Hence track sizes can not be computed. Maybe not even all tracks could be "
247 "detected. Force a full parse to prevent this."),
252 "Unable to re-sync after skipping Ogg pages in the middle of the file. Try forcing a full parse.", context);
263 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Capture pattern \"OggS\" at ", expectedOffset,
" expected."), context);
264 if (m_iterator.
resyncAt(expectedOffset)) {
266 argsToString(
"Found next capture pattern \"OggS\" at ", m_iterator.
currentPageOffset(),
". Skipped ",
269 continueFromHere =
true;
273 "Aborting after not being able to find any \"OggS\" capture patterns within 65307 bytes (from offset ", expectedOffset,
")."),
287 for (
auto &comment :
m_tags) {
293 const auto context = argsToString(
"parsing tag in Ogg page at ",
startOffset);
294 auto padding = std::uint64_t();
297 comment->parse(m_iterator, flags, padding, diag);
316 static constexpr auto noPacketsFinishOnPage = std::numeric_limits<std::uint64_t>::max();
320 if (
const auto &page = m_iterator.
pages()[i]; page.absoluteGranulePosition() != noPacketsFinishOnPage) {
322 argsToString(
"Tag spans over ", pageCount,
" pages but absolute granule position of unfinished page at ", page.startOffset(),
323 " is not set to \"-1\" (it is ", page.absoluteGranulePosition(),
")."),
328 if (
const auto &page = m_iterator.
pages()[i]; !page.isContinued()) {
330 argsToString(
"The tag is continued in Ogg page at ", page.startOffset(),
" but this page is not marked as continued packet."),
335 if (
const auto &page = m_iterator.
pages()[params.
lastPageIndex]; page.absoluteGranulePosition() == noPacketsFinishOnPage) {
337 DiagLevel::Warning, argsToString(
"Absolute granule position of final page at ", page.startOffset(),
" is set to \"-1\"."), context);
353void OggContainer::announceComment(std::size_t pageIndex, std::size_t segmentIndex,
bool lastMetaDataBlock,
GeneralMediaFormat mediaFormat)
355 auto &
tag =
m_tags.emplace_back(make_unique<OggVorbisComment>());
356 tag->
oggParams().
set(pageIndex, segmentIndex, lastMetaDataBlock, mediaFormat);
362 static const string context(
"parsing Ogg stream");
368 stream->parseHeader(diag, progress);
382void OggContainer::makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> ©Helper, vector<std::uint32_t> &newSegmentSizes,
385 const auto offset = buffer.tellp();
391 BE::getBytes(
static_cast<std::uint64_t
>(0x4F70757354616773u), copyHelper.buffer());
392 buffer.write(copyHelper.buffer(), 8);
402 buffer.write(copyHelper.buffer(), 4);
407 header.
setDataSize(
static_cast<std::uint32_t
>(buffer.tellp() - offset - 4));
410 DiagLevel::Critical,
"Size of Vorbis comment exceeds size limit for FLAC \"METADATA_BLOCK_HEADER\".",
"making Vorbis Comment");
412 buffer.seekp(offset);
414 buffer.seekp(header.
dataSize(), ios_base::cur);
421 newSegmentSizes.push_back(
static_cast<std::uint32_t
>(buffer.tellp() - offset));
426 const auto context = std::string(
"making Ogg file");
429 auto originalPath =
fileInfo().
path(), backupPath = std::string();
430 auto backupStream = NativeFileStream();
432 if (
fileInfo().saveFilePath().empty()) {
437 fileInfo().
stream().open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
438 }
catch (
const std::ios_base::failure &failure) {
440 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
446 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
451 }
catch (
const std::ios_base::failure &failure) {
452 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
464 auto tagIterator =
m_tags.cbegin(), tagEnd =
m_tags.cend();
465 if (tagIterator != tagEnd) {
466 currentParams = &(currentComment = tagIterator->get())->oggParams();
468 currentComment =
nullptr;
469 currentParams =
nullptr;
473 const OggPage *lastPage =
nullptr;
474 static constexpr auto oggPageHeaderSize = 27u;
475 auto lastPageNewOffset = std::uint64_t();
476 auto copyHelper = CopyHelper<65307>();
477 auto updatedPageOffsets = std::vector<std::uint64_t>();
478 auto nextPageOffset = std::uint64_t();
479 auto pageSequenceNumberBySerialNo = std::unordered_map<std::uint32_t, std::uint32_t>();
482 auto updateTick = 0u;
485 if (updateTick % 10) {
493 if (lastPage && currentPage.
startOffset() != nextPageOffset) {
495 if (m_iterator.
resyncAt(nextPageOffset)) {
498 if (actuallyNextPageOffset != nextPageOffset) {
500 argsToString(
"Expected Ogg page at offset ", nextPageOffset,
" but found the next Ogg page only at offset ",
501 actuallyNextPageOffset,
". Skipped ", (actuallyNextPageOffset - nextPageOffset),
" invalid bytes."),
503 nextPageOffset = actuallyNextPageOffset;
510 "Expected Ogg page at offset ", nextPageOffset,
" but could not find any further pages. Skipped the rest of the file."),
515 const auto pageSize = currentPage.
totalSize();
516 auto &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.
streamSerialNumber()];
517 lastPage = ¤tPage;
518 lastPageNewOffset =
static_cast<std::uint64_t
>(
stream().tellp());
519 nextPageOffset = currentPage.
startOffset() + pageSize;
526 auto buffer = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary);
527 auto newSegmentSizes = std::vector<std::uint32_t>();
528 newSegmentSizes.reserve(currentPage.
segmentSizes().size());
530 auto segmentIndex = std::vector<std::uint32_t>::size_type();
531 for (
const auto segmentSize : currentPage.
segmentSizes()) {
544 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
550 if (++tagIterator != tagEnd) {
551 currentParams = &(currentComment = tagIterator->get())->oggParams();
553 currentComment =
nullptr;
554 currentParams =
nullptr;
559 backupStream.seekg(
static_cast<std::streamoff
>(segmentOffset));
560 copyHelper.copy(backupStream, buffer, segmentSize);
561 newSegmentSizes.push_back(segmentSize);
567 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
570 if (++tagIterator != tagEnd) {
571 currentParams = &(currentComment = tagIterator->get())->oggParams();
573 currentComment =
nullptr;
574 currentParams =
nullptr;
578 segmentOffset += segmentSize;
583 if (
auto newSegmentSizesIterator = newSegmentSizes.cbegin(), newSegmentSizesEnd = newSegmentSizes.cend();
584 newSegmentSizesIterator != newSegmentSizesEnd) {
585 auto bytesLeft = *newSegmentSizesIterator;
586 auto continuePreviousSegment =
false, needsZeroLacingValue =
false;
588 while (newSegmentSizesIterator != newSegmentSizesEnd) {
590 updatedPageOffsets.push_back(
static_cast<std::uint64_t
>(
stream().tellp()));
592 backupStream.seekg(
static_cast<streamoff
>(currentPage.
startOffset()));
593 copyHelper.copy(backupStream,
stream(), oggPageHeaderSize);
595 auto flags = (currentPage.
headerTypeFlag() & 0xFE) | (continuePreviousSegment ? 0x01 : 0x00);
596 continuePreviousSegment =
true;
599 flags = flags & (newSegmentSizesIterator != newSegmentSizes.cbegin() ? 0xFD : 0xF);
601 stream().seekp(-22, ios_base::cur);
602 stream().put(
static_cast<char>(flags));
604 stream().seekp(12, ios_base::cur);
606 writer().writeUInt32LE(pageSequenceNumber);
608 stream().seekp(5, ios_base::cur);
611 auto segmentSizesWritten = std::int16_t();
612 auto currentSize = std::uint32_t();
613 while ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
614 while (bytesLeft > 0xFF && segmentSizesWritten < 0xFF) {
615 stream().put(
static_cast<char>(0xFF));
618 ++segmentSizesWritten;
620 if ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
622 stream().put(
static_cast<char>(bytesLeft));
623 currentSize += bytesLeft;
624 needsZeroLacingValue = bytesLeft == 0xFF;
626 ++segmentSizesWritten;
628 if (!bytesLeft && !needsZeroLacingValue) {
631 if (++newSegmentSizesIterator != newSegmentSizesEnd) {
632 bytesLeft = *newSegmentSizesIterator;
633 continuePreviousSegment =
false;
639 if (!bytesLeft && !needsZeroLacingValue) {
640 continuePreviousSegment =
false;
644 if (newSegmentSizesIterator != newSegmentSizesEnd) {
646 stream().seekp(-21 - segmentSizesWritten, ios_base::cur);
647 writer().writeInt64LE(-1);
648 stream().seekp(12, ios_base::cur);
651 backupStream.seekg(
static_cast<streamoff
>(m_iterator.
pages()[currentParams->
lastPageIndex].startOffset() + 6));
652 stream().seekp(-21 - segmentSizesWritten, ios_base::cur);
653 copyHelper.copy(backupStream,
stream(), 8);
654 stream().seekp(12, ios_base::cur);
657 stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
663 stream().put(
static_cast<char>(segmentSizesWritten));
664 stream().seekp(segmentSizesWritten, ios_base::cur);
666 copyHelper.copy(buffer,
stream(), currentSize);
668 ++pageSequenceNumber;
675 backupStream.seekg(
static_cast<streamoff
>(currentPage.
startOffset()));
676 updatedPageOffsets.push_back(
static_cast<std::uint64_t
>(
stream().tellp()));
677 copyHelper.copy(backupStream,
stream(), oggPageHeaderSize);
678 stream().seekp(-9, ios_base::cur);
679 writer().writeUInt32LE(pageSequenceNumber);
680 stream().seekp(5, ios_base::cur);
681 copyHelper.copy(backupStream,
stream(), pageSize - oggPageHeaderSize);
684 backupStream.seekg(
static_cast<streamoff
>(currentPage.
startOffset()));
685 copyHelper.copy(backupStream,
stream(), pageSize);
687 ++pageSequenceNumber;
696 if (!
fileInfo().saveFilePath().empty()) {
703 backupStream.close();
708 if (lastPage && lastPageNewOffset) {
709 const auto offset =
static_cast<std::streamoff
>(lastPageNewOffset + 5ul);
711 if (
const auto flag =
stream.get(); !(flag & 0x04)) {
712 updatedPageOffsets.emplace_back(lastPageNewOffset);
714 stream.put(
static_cast<char>(flag | 0x04));
721 for (
auto offset : updatedPageOffsets) {
722 if (updateTick++ % 10) {
The AbortableProgressFeedback class provides feedback about an ongoing operation via callbacks.
bool isAborted() const
Returns whether the operation has been aborted via tryToAbort().
void stopIfAborted() const
Throws an OperationAbortedException if aborted.
void nextStepOrStop(const std::string &step, std::uint8_t stepPercentage=0)
Throws an OperationAbortedException if aborted; otherwise the data for the next step is set.
std::iostream & stream()
Returns the related stream.
std::uint64_t startOffset() const
Returns the start offset in the related stream.
void parseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tracks of the file if not parsed yet.
CppUtilities::BinaryWriter & writer()
Returns the related BinaryWriter.
void parseTags(Diagnostics &diag, AbortableProgressFeedback &progress)
Parses the tag information if not parsed yet.
CppUtilities::TimeSpan m_duration
std::uint64_t id() const
Returns the track ID if known; otherwise returns 0.
MediaFormat format() const
Returns the format of the track if known; otherwise returns MediaFormat::Unknown.
void reportPathChanged(std::string_view newPath)
Call this function to report that the path changed.
const std::string & path() const
Returns the path of the current file.
std::uint64_t size() const
Returns size of the current file in bytes.
CppUtilities::NativeFileStream & stream()
Returns the std::fstream for the current instance.
void close()
A possibly opened std::fstream will be closed.
static std::string_view pathForOpen(std::string_view url)
Returns removes the "file:/" prefix from url to be able to pass it to functions like open(),...
void reportSizeChanged(std::uint64_t newSize)
Call this function to report that the size changed.
void updateStepPercentage(std::uint8_t stepPercentage)
Updates the current step percentage and invokes the second callback specified on construction (or the...
The Diagnostics class is a container for DiagMessage.
The class inherits from std::exception and serves as base class for exceptions thrown by the elements...
The GenericContainer class helps parsing header, track, tag and chapter information of a file.
OggStream * track(std::size_t index) override
std::vector< std::unique_ptr< OggStream > > m_tracks
MediaFileInfo & fileInfo() const
std::vector< std::unique_ptr< OggVorbisComment > > m_tags
The exception that is thrown when the data to be parsed or to be made seems invalid and therefore can...
void internalParseTags(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tags.
OggVorbisComment * tag(std::size_t index) override
Returns the tag with the specified index.
std::size_t tagCount() const override
Returns the number of tags attached to the container.
void internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the tracks.
OggContainer(MediaFileInfo &fileInfo, std::uint64_t startOffset)
Constructs a new container for the specified stream at the specified startOffset.
void internalParseHeader(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to parse the header.
void removeAllTags() override
Actually just flags all tags as removed and clears all assigned fields.
OggVorbisComment * createTag(const TagTarget &target) override
Creates a new tag.
void reset() override
Discards all parsing results.
void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) override
Internally called to make the file.
bool removeTag(Tag *tag) override
Actually just flags the specified tag as removed and clears all assigned fields.
void nextPage()
Increases the current position by one page.
void setSegmentIndex(std::vector< std::uint32_t >::size_type index)
Sets the current segment index.
void removeFilter()
Removes a previously set filter.
void setStream(std::istream &stream)
Sets the stream.
std::vector< std::uint32_t >::size_type currentSegmentIndex() const
Returns the index of the current segment (in the current page) if the iterator is valid; otherwise an...
void clear(std::istream &stream, std::uint64_t startOffset, std::uint64_t streamSize)
Sets the stream and related parameters and clears all available pages.
void previousPage()
Decreases the current position by one page.
std::uint64_t currentSegmentOffset() const
Returns the start offset of the current segment in the input stream if the iterator is valid; otherwi...
std::uint64_t currentPageOffset() const
Returns the start offset of the current Ogg page.
const OggPage & currentPage() const
Returns the current Ogg page.
const std::vector< OggPage > & pages() const
Returns a vector of containing the Ogg pages that have been fetched yet.
bool resyncAt(std::uint64_t offset)
Fetches the next page at the specified offset.
std::uint64_t startOffset() const
Returns the start offset (which has been specified when constructing the iterator).
void setPageIndex(std::vector< OggPage >::size_type index)
Sets the current page index.
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...
void reset()
Resets the iterator to point at the first segment of the first page (matching the filter if set).
void setFilter(std::uint32_t streamSerialId)
Allows to filter pages by the specified streamSerialId.
void ignore(std::size_t count=1)
Advances the position of the next character to be read from the Ogg stream by count bytes.
The OggPage class is used to parse Ogg pages.
std::uint32_t sequenceNumber() const
Returns the page sequence number.
std::uint32_t dataSize() const
Returns the data size in byte.
std::uint32_t totalSize() const
Returns the total size of the page in byte.
std::uint32_t streamSerialNumber() const
Returns the stream serial number.
const std::vector< std::uint32_t > & segmentSizes() const
Returns the sizes of the segments of the page in byte.
std::uint8_t headerTypeFlag() const
Returns the header type flag.
std::uint64_t startOffset() const
Returns the start offset of the page.
std::uint32_t checksum() const
Returns the page checksum.
static std::uint32_t computeChecksum(std::istream &stream, std::uint64_t startOffset)
Computes the actual checksum of the page read from the specified stream at the specified startOffset.
static void updateChecksum(std::iostream &stream, std::uint64_t startOffset)
Updates the checksum of the page read from the specified stream at the specified startOffset.
Implementation of TagParser::AbstractTrack for Ogg streams.
std::size_t startPage() const
The exception that is thrown when an operation has been stopped and thus not successfully completed b...
The TagTarget class specifies the target of a tag.
const IdContainerType & tracks() const
Returns the tracks.
The Tag class is used to store, read and write tag information.
const TagTarget & target() const
The exception that is thrown when the data to be parsed is truncated and therefore can not be parsed ...
TAG_PARSER_EXPORT void handleFailureAfterFileModifiedCanonical(MediaFileInfo &fileInfo, const std::string &originalPath, const std::string &backupPath, CppUtilities::NativeFileStream &outputStream, CppUtilities::NativeFileStream &backupStream, Diagnostics &diag, const std::string &context="making file")
Handles a failure/abort which occurred after the file has been modified.
TAG_PARSER_EXPORT void createBackupFileCanonical(const std::string &backupDir, std::string &originalPath, std::string &backupPath, CppUtilities::NativeFileStream &originalStream, CppUtilities::NativeFileStream &backupStream)
Creates a backup file like createBackupFile() but canonicalizes originalPath before doing the backup.
Contains all classes and functions of the TagInfo library.
GeneralMediaFormat
The GeneralMediaFormat enum specifies the general format of media data (PCM, MPEG-4,...
The OggParameter struct holds the Ogg parameter for a VorbisComment.
GeneralMediaFormat streamFormat
std::size_t lastPageIndex
std::size_t lastSegmentIndex
std::size_t firstSegmentIndex
void set(std::size_t pageIndex, std::size_t segmentIndex, bool lastMetaDataBlock, GeneralMediaFormat streamFormat=GeneralMediaFormat::Vorbis)
Sets the firstPageIndex/lastPageIndex, the firstSegmentIndex/lastSegmentIndex, whether the associated...
std::size_t firstPageIndex