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/copy.h>
28 return "Vorbis comment (in FLAC stream)";
30 return "Vorbis comment (in Opus stream)";
32 return "Vorbis comment (in Theora stream)";
34 return "Vorbis comment";
48 , m_iterator(fileInfo.stream(), startOffset, fileInfo.size())
49 , m_validateChecksums(false)
74 if (!target.
tracks().empty()) {
91 }
else if (!
m_tags.empty()) {
93 m_tags.front()->oggParams().removed =
false;
94 return m_tags.front().get();
108 m_tags.back()->setTarget(target);
109 return m_tags.back().get();
158 for (
auto &existingTag :
m_tags) {
159 if (
static_cast<Tag *
>(existingTag.get()) ==
tag) {
160 existingTag->removeAllFields();
161 existingTag->oggParams().removed =
true;
180 for (
auto &existingTag :
m_tags) {
181 existingTag->removeAllFields();
182 existingTag->oggParams().removed =
true;
188 CPP_UTILITIES_UNUSED(progress)
190 static const string context(
"parsing OGG bitstream header");
191 bool pagesSkipped =
false, continueFromHere =
false;
197 continueFromHere ? [&] { continueFromHere = false; }() : m_iterator.
nextPage()) {
203 "The denoted checksum of the OGG page at ", m_iterator.
currentSegmentOffset(),
" does not match the computed checksum."),
207 std::uint64_t lastNewStreamOffset = 0;
208 if (
const auto streamIndex = m_streamsBySerialNo.find(page.
streamSerialNumber()); streamIndex != m_streamsBySerialNo.end()) {
221 if (
stream->m_currentSequenceNumber) {
223 argsToString(
"Page of stream ", page.
streamSerialNumber(),
" missing; page sequence number ",
stream->m_currentSequenceNumber,
229 ++
stream->m_currentSequenceNumber;
234 && (page.
startOffset() - lastNewStreamOffset) > (20 * 0x100000)) {
238 for (
auto &trackStream :
m_tracks) {
239 trackStream->m_currentSequenceNumber = 0;
240 trackStream->m_size = 0;
242 pagesSkipped = continueFromHere =
true;
244 argsToString(
"Pages in the middle of the file (", dataSizeToString(resyncedPage.
startOffset() - page.
startOffset()),
245 ") have been skipped to improve parsing speed. Hence track sizes can not be computed. Maybe not even all tracks could be "
246 "detected. Force a full parse to prevent this."),
251 "Unable to re-sync after skipping OGG pages in the middle of the file. Try forcing a full parse.", context);
262 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Capture pattern \"OggS\" at ", expectedOffset,
" expected."), context);
263 if (m_iterator.
resyncAt(expectedOffset)) {
265 argsToString(
"Found next capture pattern \"OggS\" at ", m_iterator.
currentPageOffset(),
". Skipped ",
268 continueFromHere =
true;
272 "Aborting after not being able to find any \"OggS\" capture patterns within 65307 bytes (from offset ", expectedOffset,
")."),
300 diag.emplace_back(
DiagLevel::Critical,
"Stream format not supported.",
"parsing tags from OGG streams");
318void OggContainer::announceComment(std::size_t pageIndex, std::size_t segmentIndex,
bool lastMetaDataBlock,
GeneralMediaFormat mediaFormat)
320 m_tags.emplace_back(make_unique<OggVorbisComment>());
321 m_tags.back()->oggParams().set(pageIndex, segmentIndex, lastMetaDataBlock, mediaFormat);
326 static const string context(
"parsing OGG stream");
332 stream->parseHeader(diag, progress);
346void OggContainer::makeVorbisCommentSegment(stringstream &buffer,
CopyHelper<65307> ©Helper, vector<std::uint32_t> &newSegmentSizes,
349 const auto offset = buffer.tellp();
355 BE::getBytes(
static_cast<std::uint64_t
>(0x4F70757354616773u), copyHelper.buffer());
356 buffer.write(copyHelper.buffer(), 8);
366 buffer.write(copyHelper.buffer(), 4);
371 header.
setDataSize(
static_cast<std::uint32_t
>(buffer.tellp() - offset - 4));
374 DiagLevel::Critical,
"Size of Vorbis comment exceeds size limit for FLAC \"METADATA_BLOCK_HEADER\".",
"making Vorbis Comment");
376 buffer.seekp(offset);
378 buffer.seekp(header.
dataSize(), ios_base::cur);
383 newSegmentSizes.push_back(
static_cast<std::uint32_t
>(buffer.tellp() - offset));
388 const string context(
"making OGG file");
392 NativeFileStream backupStream;
394 if (
fileInfo().saveFilePath().empty()) {
399 fileInfo().
stream().open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
400 }
catch (
const std::ios_base::failure &failure) {
402 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
408 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
413 }
catch (
const std::ios_base::failure &failure) {
414 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
426 auto tagIterator =
m_tags.cbegin(), tagEnd =
m_tags.cend();
427 if (tagIterator != tagEnd) {
428 currentParams = &(currentComment = tagIterator->get())->oggParams();
430 currentComment =
nullptr;
431 currentParams =
nullptr;
436 vector<std::uint64_t> updatedPageOffsets;
437 const OggPage *lastPage =
nullptr;
438 std::uint64_t nextPageOffset;
439 unordered_map<std::uint32_t, std::uint32_t> pageSequenceNumberBySerialNo;
442 auto updateTick = 0u;
445 if (updateTick % 10) {
453 if (lastPage && currentPage.
startOffset() != nextPageOffset) {
455 if (m_iterator.
resyncAt(nextPageOffset)) {
458 if (actuallyNextPageOffset != nextPageOffset) {
460 argsToString(
"Expected OGG page at offset ", nextPageOffset,
" but found the next OGG page only at offset ",
461 actuallyNextPageOffset,
". Skipped ", (actuallyNextPageOffset - nextPageOffset),
" invalid bytes."),
463 nextPageOffset = actuallyNextPageOffset;
470 "Expected OGG page at offset ", nextPageOffset,
" but could not find any further pages. Skipped the rest of the file."),
475 const auto pageSize = currentPage.
totalSize();
476 std::uint32_t &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.
streamSerialNumber()];
477 lastPage = ¤tPage;
478 nextPageOffset = currentPage.
startOffset() + pageSize;
485 stringstream buffer(ios_base::in | ios_base::out | ios_base::binary);
486 vector<std::uint32_t> newSegmentSizes;
487 newSegmentSizes.reserve(currentPage.
segmentSizes().size());
489 vector<std::uint32_t>::size_type segmentIndex = 0;
490 for (
const auto segmentSize : currentPage.
segmentSizes()) {
503 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
509 if (++tagIterator != tagEnd) {
510 currentParams = &(currentComment = tagIterator->get())->oggParams();
512 currentComment =
nullptr;
513 currentParams =
nullptr;
518 backupStream.seekg(
static_cast<streamoff
>(segmentOffset));
519 copyHelper.copy(backupStream, buffer, segmentSize);
520 newSegmentSizes.push_back(segmentSize);
526 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
529 if (++tagIterator != tagEnd) {
530 currentParams = &(currentComment = tagIterator->get())->oggParams();
532 currentComment =
nullptr;
533 currentParams =
nullptr;
537 segmentOffset += segmentSize;
542 if (
auto newSegmentSizesIterator = newSegmentSizes.cbegin(), newSegmentSizesEnd = newSegmentSizes.cend();
543 newSegmentSizesIterator != newSegmentSizesEnd) {
544 auto bytesLeft = *newSegmentSizesIterator;
545 auto continuePreviousSegment =
false, needsZeroLacingValue =
false;
547 while (newSegmentSizesIterator != newSegmentSizesEnd) {
549 backupStream.seekg(
static_cast<streamoff
>(currentPage.
startOffset()));
550 updatedPageOffsets.push_back(
static_cast<std::uint64_t
>(
stream().tellp()));
551 copyHelper.copy(backupStream,
stream(), 27);
553 stream().seekp(-22, ios_base::cur);
554 stream().put(
static_cast<char>(currentPage.
headerTypeFlag() & (continuePreviousSegment ? 0xFF : 0xFE)));
555 continuePreviousSegment =
true;
557 stream().seekp(12, ios_base::cur);
558 writer().writeUInt32LE(pageSequenceNumber);
559 stream().seekp(5, ios_base::cur);
560 std::int16_t segmentSizesWritten = 0;
563 std::uint32_t currentSize = 0;
564 while ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
565 while (bytesLeft > 0xFF && segmentSizesWritten < 0xFF) {
566 stream().put(
static_cast<char>(0xFF));
569 ++segmentSizesWritten;
571 if ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
573 stream().put(
static_cast<char>(bytesLeft));
574 currentSize += bytesLeft;
575 needsZeroLacingValue = bytesLeft == 0xFF;
577 ++segmentSizesWritten;
579 if (!bytesLeft && !needsZeroLacingValue) {
582 if (++newSegmentSizesIterator != newSegmentSizesEnd) {
583 bytesLeft = *newSegmentSizesIterator;
584 continuePreviousSegment =
false;
590 if (!bytesLeft && !needsZeroLacingValue) {
591 continuePreviousSegment =
false;
597 stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
598 stream().put(
static_cast<char>(segmentSizesWritten));
599 stream().seekp(segmentSizesWritten, ios_base::cur);
601 copyHelper.copy(buffer,
stream(), currentSize);
603 ++pageSequenceNumber;
610 backupStream.seekg(
static_cast<streamoff
>(currentPage.
startOffset()));
611 updatedPageOffsets.push_back(
static_cast<std::uint64_t
>(
stream().tellp()));
612 copyHelper.copy(backupStream,
stream(), 27);
613 stream().seekp(-9, ios_base::cur);
614 writer().writeUInt32LE(pageSequenceNumber);
615 stream().seekp(5, ios_base::cur);
616 copyHelper.copy(backupStream,
stream(), pageSize - 27);
619 backupStream.seekg(
static_cast<streamoff
>(currentPage.
startOffset()));
620 copyHelper.copy(backupStream,
stream(), pageSize);
622 ++pageSequenceNumber;
631 if (!
fileInfo().saveFilePath().empty()) {
637 backupStream.close();
644 for (
auto offset : updatedPageOffsets) {
645 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
Returns the related file info.
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.
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 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
Returns the target of tag.
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.
constexpr TAG_PARSER_EXPORT std::string_view comment()
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
std::size_t firstPageIndex