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>
24 const char *OggVorbisComment::typeName()
const
26 switch (m_oggParams.streamFormat) {
28 return "Vorbis comment (in FLAC stream)";
30 return "Vorbis comment (in Opus stream)";
31 case GeneralMediaFormat::Theora:
32 return "Vorbis comment (in Theora stream)";
34 return "Vorbis comment";
46 OggContainer::OggContainer(
MediaFileInfo &fileInfo, std::uint64_t startOffset)
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 static const string context(
"parsing OGG bitstream header");
189 bool pagesSkipped =
false;
199 "The denoted checksum of the OGG page at ", m_iterator.
currentSegmentOffset(),
" does not match the computed checksum."),
203 std::uint64_t lastNewStreamOffset = 0;
207 }
catch (
const out_of_range &) {
215 if (
stream->m_currentSequenceNumber) {
216 diag.emplace_back(
DiagLevel::Warning,
"Page is missing (page sequence number omitted).", context);
220 ++
stream->m_currentSequenceNumber;
225 && (page.
startOffset() - lastNewStreamOffset) > (20 * 0x100000)) {
232 argsToString(
"Pages in the middle of the file (", dataSizeToString(resyncedPage.
startOffset() - page.
startOffset()),
233 ") have been skipped to improve parsing speed. Hence track sizes can not be computed. Maybe not even all tracks could be "
234 "detected. Force a full parse to prevent this."),
239 "Unable to re-sync after skipping OGG pages in the middle of the file. Try forcing a full parse.", context);
283 diag.emplace_back(
DiagLevel::Critical,
"Stream format not supported.",
"parsing tags from OGG streams");
301 void OggContainer::announceComment(std::size_t pageIndex, std::size_t segmentIndex,
bool lastMetaDataBlock,
GeneralMediaFormat mediaFormat)
303 m_tags.emplace_back(make_unique<OggVorbisComment>());
304 m_tags.back()->oggParams().set(pageIndex, segmentIndex, lastMetaDataBlock, mediaFormat);
309 static const string context(
"parsing OGG stream");
312 stream->parseHeader(diag);
326 void OggContainer::makeVorbisCommentSegment(stringstream &buffer,
CopyHelper<65307> ©Helper, vector<std::uint32_t> &newSegmentSizes,
329 const auto offset = buffer.tellp();
335 BE::getBytes(static_cast<std::uint64_t>(0x4F70757354616773u), copyHelper.buffer());
336 buffer.write(copyHelper.buffer(), 8);
346 buffer.write(copyHelper.buffer(), 4);
351 header.
setDataSize(static_cast<std::uint32_t>(buffer.tellp() - offset - 4));
354 DiagLevel::Critical,
"Size of Vorbis comment exceeds size limit for FLAC \"METADATA_BLOCK_HEADER\".",
"making Vorbis Comment");
356 buffer.seekp(offset);
358 buffer.seekp(header.
dataSize(), ios_base::cur);
363 newSegmentSizes.push_back(static_cast<std::uint32_t>(buffer.tellp() - offset));
368 const string context(
"making OGG file");
369 progress.
updateStep(
"Prepare for rewriting OGG file ...");
372 NativeFileStream backupStream;
374 if (
fileInfo().saveFilePath().empty()) {
380 }
catch (
const std::ios_base::failure &failure) {
382 DiagLevel::Critical, argsToString(
"Creation of temporary file (to rewrite the original file) failed: ", failure.what()), context);
388 backupStream.exceptions(ios_base::badbit | ios_base::failbit);
392 }
catch (
const std::ios_base::failure &failure) {
393 diag.emplace_back(
DiagLevel::Critical, argsToString(
"Opening streams to write output file failed: ", failure.what()), context);
402 auto tagIterator =
m_tags.cbegin(), tagEnd =
m_tags.cend();
403 if (tagIterator != tagEnd) {
404 currentParams = &(currentComment = tagIterator->get())->oggParams();
406 currentComment =
nullptr;
407 currentParams =
nullptr;
412 vector<std::uint64_t> updatedPageOffsets;
413 unordered_map<std::uint32_t, std::uint32_t> pageSequenceNumberBySerialNo;
418 const auto pageSize = currentPage.
totalSize();
419 std::uint32_t &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.
streamSerialNumber()];
425 stringstream buffer(ios_base::in | ios_base::out | ios_base::binary);
426 vector<std::uint32_t> newSegmentSizes;
427 newSegmentSizes.reserve(currentPage.
segmentSizes().size());
429 vector<std::uint32_t>::size_type segmentIndex = 0;
430 for (
const auto segmentSize : currentPage.
segmentSizes()) {
442 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(static_cast<streamoff>(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;
481 auto newSegmentSizesIterator = newSegmentSizes.cbegin(), newSegmentSizesEnd = newSegmentSizes.cend();
482 bool continuePreviousSegment =
false;
483 if (newSegmentSizesIterator != newSegmentSizesEnd) {
484 std::uint32_t bytesLeft = *newSegmentSizesIterator;
486 while (newSegmentSizesIterator != newSegmentSizesEnd) {
488 backupStream.seekg(static_cast<streamoff>(currentPage.
startOffset()));
489 updatedPageOffsets.push_back(static_cast<std::uint64_t>(
stream().tellp()));
490 copyHelper.copy(backupStream,
stream(), 27);
492 stream().seekp(-22, ios_base::cur);
493 stream().put(static_cast<char>(currentPage.
headerTypeFlag() & (continuePreviousSegment ? 0xFF : 0xFE)));
494 continuePreviousSegment =
true;
496 stream().seekp(12, ios_base::cur);
497 writer().writeUInt32LE(pageSequenceNumber);
498 stream().seekp(5, ios_base::cur);
499 std::int16_t segmentSizesWritten = 0;
502 std::uint32_t currentSize = 0;
503 while (bytesLeft && segmentSizesWritten < 0xFF) {
504 while (bytesLeft >= 0xFF && segmentSizesWritten < 0xFF) {
505 stream().put(static_cast<char>(0xFF));
508 ++segmentSizesWritten;
510 if (bytesLeft && segmentSizesWritten < 0xFF) {
512 stream().put(static_cast<char>(bytesLeft));
513 currentSize += bytesLeft;
515 ++segmentSizesWritten;
520 if (++newSegmentSizesIterator != newSegmentSizesEnd) {
521 bytesLeft = *newSegmentSizesIterator;
522 continuePreviousSegment =
false;
529 continuePreviousSegment =
false;
535 stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
536 stream().put(static_cast<char>(segmentSizesWritten));
537 stream().seekp(segmentSizesWritten, ios_base::cur);
539 copyHelper.copy(buffer,
stream(), currentSize);
541 ++pageSequenceNumber;
548 backupStream.seekg(static_cast<streamoff>(currentPage.
startOffset()));
549 updatedPageOffsets.push_back(static_cast<std::uint64_t>(
stream().tellp()));
550 copyHelper.copy(backupStream,
stream(), 27);
551 stream().seekp(-9, ios_base::cur);
552 writer().writeUInt32LE(pageSequenceNumber);
553 stream().seekp(5, ios_base::cur);
554 copyHelper.copy(backupStream,
stream(), pageSize - 27);
557 backupStream.seekg(static_cast<streamoff>(currentPage.
startOffset()));
558 copyHelper.copy(backupStream,
stream(), pageSize);
560 ++pageSequenceNumber;
568 if (!
fileInfo().saveFilePath().empty()) {
574 backupStream.close();
579 for (
auto offset : updatedPageOffsets) {