3#include "../flac/flacmetadata.h"
5#include "../backuphelper.h"
6#include "../mediafileinfo.h"
7#include "../progressfeedback.h"
8#include "../tagtarget.h"
10#include <c++utilities/conversion/stringbuilder.h>
11#include <c++utilities/io/copy.h>
29 return "Vorbis comment (in FLAC stream)";
31 return "Vorbis comment (in Opus stream)";
33 return "Vorbis comment (in Theora stream)";
35 return "Vorbis comment";
49 , m_iterator(fileInfo.stream(), startOffset, fileInfo.size())
50 , m_validateChecksums(false)
75 if (!target.
tracks().empty()) {
92 }
else if (!
m_tags.empty()) {
94 m_tags.front()->oggParams().removed =
false;
95 return m_tags.front().get();
109 m_tags.back()->setTarget(target);
110 return m_tags.back().get();
159 for (
auto &existingTag :
m_tags) {
160 if (
static_cast<Tag *
>(existingTag.get()) ==
tag) {
162 existingTag->oggParams().removed =
true;
181 for (
auto &existingTag :
m_tags) {
182 existingTag->removeAllFields();
183 existingTag->oggParams().removed =
true;
189 CPP_UTILITIES_UNUSED(progress)
191 static const string context(
"parsing OGG bitstream header");
192 bool pagesSkipped =
false, continueFromHere =
false;
198 continueFromHere ? [&] { continueFromHere =
false; }() : m_iterator.
nextPage()) {
204 "The denoted checksum of the OGG page at ", m_iterator.
currentSegmentOffset(),
" does not match the computed checksum."),
208 std::uint64_t lastNewStreamOffset = 0;
209 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,
")."),
283 for (
auto &comment :
m_tags) {
302 diag.emplace_back(
DiagLevel::Critical,
"Stream format not supported.",
"parsing tags from OGG streams");
320void OggContainer::announceComment(std::size_t pageIndex, std::size_t segmentIndex,
bool lastMetaDataBlock,
GeneralMediaFormat mediaFormat)
322 auto &
tag =
m_tags.emplace_back(make_unique<OggVorbisComment>());
323 tag->
oggParams().
set(pageIndex, segmentIndex, lastMetaDataBlock, mediaFormat);
329 static const string context(
"parsing OGG stream");
335 stream->parseHeader(diag, progress);
349void OggContainer::makeVorbisCommentSegment(stringstream &buffer, CopyHelper<65307> ©Helper, vector<std::uint32_t> &newSegmentSizes,
352 const auto offset = buffer.tellp();
358 BE::getBytes(
static_cast<std::uint64_t
>(0x4F70757354616773u), copyHelper.buffer());
359 buffer.write(copyHelper.buffer(), 8);
369 buffer.write(copyHelper.buffer(), 4);
374 header.
setDataSize(
static_cast<std::uint32_t
>(buffer.tellp() - offset - 4));
377 DiagLevel::Critical,
"Size of Vorbis comment exceeds size limit for FLAC \"METADATA_BLOCK_HEADER\".",
"making Vorbis Comment");
379 buffer.seekp(offset);
381 buffer.seekp(header.
dataSize(), ios_base::cur);
386 newSegmentSizes.push_back(
static_cast<std::uint32_t
>(buffer.tellp() - offset));
391 const string context(
"making OGG file");
395 NativeFileStream backupStream;
397 if (
fileInfo().saveFilePath().empty()) {
402 fileInfo().
stream().open(originalPath, ios_base::out | ios_base::binary | ios_base::trunc);
403 }
catch (
const std::ios_base::failure &failure) {
405 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
411 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
416 }
catch (
const std::ios_base::failure &failure) {
417 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
429 auto tagIterator =
m_tags.cbegin(), tagEnd =
m_tags.cend();
430 if (tagIterator != tagEnd) {
431 currentParams = &(currentComment = tagIterator->get())->oggParams();
433 currentComment =
nullptr;
434 currentParams =
nullptr;
438 CopyHelper<65307> copyHelper;
439 vector<std::uint64_t> updatedPageOffsets;
440 const OggPage *lastPage =
nullptr;
441 std::uint64_t nextPageOffset;
442 unordered_map<std::uint32_t, std::uint32_t> pageSequenceNumberBySerialNo;
445 auto updateTick = 0u;
448 if (updateTick % 10) {
456 if (lastPage && currentPage.
startOffset() != nextPageOffset) {
458 if (m_iterator.
resyncAt(nextPageOffset)) {
461 if (actuallyNextPageOffset != nextPageOffset) {
463 argsToString(
"Expected OGG page at offset ", nextPageOffset,
" but found the next OGG page only at offset ",
464 actuallyNextPageOffset,
". Skipped ", (actuallyNextPageOffset - nextPageOffset),
" invalid bytes."),
466 nextPageOffset = actuallyNextPageOffset;
473 "Expected OGG page at offset ", nextPageOffset,
" but could not find any further pages. Skipped the rest of the file."),
478 const auto pageSize = currentPage.
totalSize();
479 std::uint32_t &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.
streamSerialNumber()];
480 lastPage = ¤tPage;
481 nextPageOffset = currentPage.
startOffset() + pageSize;
488 stringstream buffer(ios_base::in | ios_base::out | ios_base::binary);
489 vector<std::uint32_t> newSegmentSizes;
490 newSegmentSizes.reserve(currentPage.
segmentSizes().size());
492 vector<std::uint32_t>::size_type segmentIndex = 0;
493 for (
const auto segmentSize : currentPage.
segmentSizes()) {
506 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
512 if (++tagIterator != tagEnd) {
513 currentParams = &(currentComment = tagIterator->get())->oggParams();
515 currentComment =
nullptr;
516 currentParams =
nullptr;
521 backupStream.seekg(
static_cast<streamoff
>(segmentOffset));
522 copyHelper.copy(backupStream, buffer, segmentSize);
523 newSegmentSizes.push_back(segmentSize);
529 makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams, diag);
532 if (++tagIterator != tagEnd) {
533 currentParams = &(currentComment = tagIterator->get())->oggParams();
535 currentComment =
nullptr;
536 currentParams =
nullptr;
540 segmentOffset += segmentSize;
545 if (
auto newSegmentSizesIterator = newSegmentSizes.cbegin(), newSegmentSizesEnd = newSegmentSizes.cend();
546 newSegmentSizesIterator != newSegmentSizesEnd) {
547 auto bytesLeft = *newSegmentSizesIterator;
548 auto continuePreviousSegment =
false, needsZeroLacingValue =
false;
550 while (newSegmentSizesIterator != newSegmentSizesEnd) {
552 backupStream.seekg(
static_cast<streamoff
>(currentPage.
startOffset()));
553 updatedPageOffsets.push_back(
static_cast<std::uint64_t
>(
stream().tellp()));
554 copyHelper.copy(backupStream,
stream(), 27);
556 stream().seekp(-22, ios_base::cur);
557 stream().put(
static_cast<char>(currentPage.
headerTypeFlag() & (continuePreviousSegment ? 0xFF : 0xFE)));
558 continuePreviousSegment =
true;
560 stream().seekp(12, ios_base::cur);
561 writer().writeUInt32LE(pageSequenceNumber);
562 stream().seekp(5, ios_base::cur);
563 std::int16_t segmentSizesWritten = 0;
566 std::uint32_t currentSize = 0;
567 while ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
568 while (bytesLeft > 0xFF && segmentSizesWritten < 0xFF) {
569 stream().put(
static_cast<char>(0xFF));
572 ++segmentSizesWritten;
574 if ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
576 stream().put(
static_cast<char>(bytesLeft));
577 currentSize += bytesLeft;
578 needsZeroLacingValue = bytesLeft == 0xFF;
580 ++segmentSizesWritten;
582 if (!bytesLeft && !needsZeroLacingValue) {
585 if (++newSegmentSizesIterator != newSegmentSizesEnd) {
586 bytesLeft = *newSegmentSizesIterator;
587 continuePreviousSegment =
false;
593 if (!bytesLeft && !needsZeroLacingValue) {
594 continuePreviousSegment =
false;
600 stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
601 stream().put(
static_cast<char>(segmentSizesWritten));
602 stream().seekp(segmentSizesWritten, ios_base::cur);
604 copyHelper.copy(buffer,
stream(), currentSize);
606 ++pageSequenceNumber;
613 backupStream.seekg(
static_cast<streamoff
>(currentPage.
startOffset()));
614 updatedPageOffsets.push_back(
static_cast<std::uint64_t
>(
stream().tellp()));
615 copyHelper.copy(backupStream,
stream(), 27);
616 stream().seekp(-9, ios_base::cur);
617 writer().writeUInt32LE(pageSequenceNumber);
618 stream().seekp(5, ios_base::cur);
619 copyHelper.copy(backupStream,
stream(), pageSize - 27);
622 backupStream.seekg(
static_cast<streamoff
>(currentPage.
startOffset()));
623 copyHelper.copy(backupStream,
stream(), pageSize);
625 ++pageSequenceNumber;
634 if (!
fileInfo().saveFilePath().empty()) {
640 backupStream.close();
647 for (
auto offset : updatedPageOffsets) {
648 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...
void removeAllFields()
Removes all fields from the tag.
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 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