Compare commits

..

1 Commits

Author SHA1 Message Date
Martchus 41a4c0c165 WIP: Make use of copy-overloads using sendfile64() 2023-04-29 12:08:58 +02:00
66 changed files with 422 additions and 960 deletions

4
.gitignore vendored
View File

@ -42,7 +42,3 @@ Makefile*
# clang-format # clang-format
/.clang-format /.clang-format
# file with language codes one might store in the a local checkout
iso_639-2.json

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# meta data # meta data
project(tagparser) project(tagparser)
@ -8,8 +8,8 @@ set(META_APP_NAME "Tag Parser")
set(META_APP_AUTHOR "Martchus") set(META_APP_AUTHOR "Martchus")
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
set(META_APP_DESCRIPTION "C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags") set(META_APP_DESCRIPTION "C++ library for reading and writing MP4 (iTunes), ID3, Vorbis, Opus, FLAC and Matroska tags")
set(META_VERSION_MAJOR 12) set(META_VERSION_MAJOR 11)
set(META_VERSION_MINOR 2) set(META_VERSION_MINOR 6)
set(META_VERSION_PATCH 0) set(META_VERSION_PATCH 0)
set(META_REQUIRED_CPP_UNIT_VERSION 1.14.0) set(META_REQUIRED_CPP_UNIT_VERSION 1.14.0)
set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON) set(META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION ON)
@ -185,9 +185,8 @@ set(RES_FILES "${LANGUAGE_HEADER_ISO_639_2}")
set(CONFIGURATION_PACKAGE_SUFFIX set(CONFIGURATION_PACKAGE_SUFFIX
"" ""
CACHE STRING "sets the suffix for find_package() calls to packages configured via c++utilities") CACHE STRING "sets the suffix for find_package() calls to packages configured via c++utilities")
find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.21.0 REQUIRED) find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.19.0 REQUIRED)
use_cpp_utilities(VISIBILITY PUBLIC) use_cpp_utilities(VISIBILITY PUBLIC)
list(APPEND META_PRIVATE_COMPILE_DEFINITIONS CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS)
# link against a possibly required extra library for std::filesystem # link against a possibly required extra library for std::filesystem
use_standard_filesystem() use_standard_filesystem()

View File

@ -7,19 +7,11 @@ The tag library can read and write the following tag formats:
* iTunes-style MP4/M4A tags (MP4-DASH is supported) * iTunes-style MP4/M4A tags (MP4-DASH is supported)
* ID3v1 and ID3v2 tags * ID3v1 and ID3v2 tags
* conversion between ID3v1 and different versions of ID3v2 is possible * conversion between ID3v1 and different versions of ID3v2 is possible
* mainly for use in MP3 files but can be added to any kind of file
* Vorbis, Opus and FLAC comments in Ogg streams * Vorbis, Opus and FLAC comments in Ogg streams
* cover art via "METADATA_BLOCK_PICTURE" is supported * cover art via "METADATA_BLOCK_PICTURE" is supported
* Vorbis comments and "METADATA_BLOCK_PICTURE" in raw FLAC streams * Vorbis comments and "METADATA_BLOCK_PICTURE" in raw FLAC streams
* Matroska/WebM tags and attachments * Matroska/WebM tags and attachments
Further remarks:
* Unsupported file contents (such as unsupported tag formats) are *generally* preserved as-is.
* Note that APE tags are *not* supported. APE tags in the beginning of a file are strongly
unrecommended and thus discarded when applying changes. APE tags at the end of the file
are preserved as-is when applying changes.
## File layout options ## File layout options
### Tag position ### Tag position
The library allows you to choose whether tags should be placed at the beginning or at The library allows you to choose whether tags should be placed at the beginning or at
@ -67,7 +59,7 @@ The library is aware of different text encodings and can convert between differe
### Further documentation ### Further documentation
For more examples check out the command line interface of [Tag Editor](https://github.com/Martchus/tageditor). For more examples check out the command line interface of [Tag Editor](https://github.com/Martchus/tageditor).
API documentation can be generated using Doxygen with `cmake --build … --target tagparser_apidoc`. API documentation can be generated using Doxygen with `make tagparser_apidoc`.
## Bugs, stability ## Bugs, stability
Bugs can be reported on GitHub. Bugs can be reported on GitHub.
@ -94,6 +86,6 @@ the ["Building this straight"](https://github.com/Martchus/tageditor#building-th
More TODOs are tracked in the [issue section at GitHub](https://github.com/Martchus/tagparser/issues). More TODOs are tracked in the [issue section at GitHub](https://github.com/Martchus/tagparser/issues).
## Copyright notice and license ## Copyright notice and license
Copyright © 2015-2024 Marius Kittler Copyright © 2015-2023 Marius Kittler
All code is licensed under [GPL-2-or-later](LICENSE). All code is licensed under [GPL-2-or-later](LICENSE).

View File

@ -955,7 +955,7 @@ std::int16_t AacFrameElementParser::sbrHuffmanDec(SbrHuffTab table)
void AacFrameElementParser::parseSbrGrid(std::shared_ptr<AacSbrInfo> &sbr, std::uint8_t channel) void AacFrameElementParser::parseSbrGrid(std::shared_ptr<AacSbrInfo> &sbr, std::uint8_t channel)
{ {
std::uint8_t tmp, bsEnvCount = 0; std::uint8_t tmp, bsEnvCount;
//byte bsRelCount0, bsRelCount1; //byte bsRelCount0, bsRelCount1;
switch ((sbr->bsFrameClass[channel] = m_reader.readBits<std::uint8_t>(2))) { switch ((sbr->bsFrameClass[channel] = m_reader.readBits<std::uint8_t>(2))) {
using namespace BsFrameClasses; using namespace BsFrameClasses;

View File

@ -14,9 +14,6 @@ using namespace CppUtilities;
namespace TagParser { namespace TagParser {
/// \brief The AbstractAttachmentPrivate struct contains private fields of the AbstractAttachment class.
struct AbstractAttachmentPrivate {};
/*! /*!
* \class TagParser::StreamDataBlock * \class TagParser::StreamDataBlock
* \brief The StreamDataBlock class is a reference to a certain data block of a stream. * \brief The StreamDataBlock class is a reference to a certain data block of a stream.
@ -130,23 +127,6 @@ FileDataBlock::~FileDataBlock()
* \brief The AbstractAttachment class parses and stores attachment information. * \brief The AbstractAttachment class parses and stores attachment information.
*/ */
/*!
* \brief Constructs a new attachment.
*/
AbstractAttachment::AbstractAttachment()
: m_id(0)
, m_isDataFromFile(false)
, m_ignored(false)
{
}
/*!
* \brief Destroys the attachment.
*/
AbstractAttachment::~AbstractAttachment()
{
}
/*! /*!
* \brief Returns a label for the track. * \brief Returns a label for the track.
*/ */

View File

@ -102,8 +102,6 @@ inline const MediaFileInfo *FileDataBlock::fileInfo() const
return m_fileInfo.get(); return m_fileInfo.get();
} }
struct AbstractAttachmentPrivate;
class TAG_PARSER_EXPORT AbstractAttachment { class TAG_PARSER_EXPORT AbstractAttachment {
public: public:
const std::string &description() const; const std::string &description() const;
@ -125,8 +123,7 @@ public:
bool isEmpty() const; bool isEmpty() const;
protected: protected:
explicit AbstractAttachment(); AbstractAttachment();
virtual ~AbstractAttachment();
private: private:
std::string m_description; std::string m_description;
@ -134,11 +131,20 @@ private:
std::string m_mimeType; std::string m_mimeType;
std::uint64_t m_id; std::uint64_t m_id;
std::unique_ptr<StreamDataBlock> m_data; std::unique_ptr<StreamDataBlock> m_data;
std::unique_ptr<AbstractAttachmentPrivate> m_p;
bool m_isDataFromFile; bool m_isDataFromFile;
bool m_ignored; bool m_ignored;
}; };
/*!
* \brief Constructs a new attachment.
*/
inline AbstractAttachment::AbstractAttachment()
: m_id(0)
, m_isDataFromFile(false)
, m_ignored(false)
{
}
/*! /*!
* \brief Returns a description of the attachment. * \brief Returns a description of the attachment.
*/ */

View File

@ -8,9 +8,6 @@ using namespace CppUtilities;
namespace TagParser { namespace TagParser {
/// \brief The AbstractChapterPrivate struct contains private fields of the AbstractChapter class.
struct AbstractChapterPrivate {};
/*! /*!
* \class TagParser::AbstractChapter * \class TagParser::AbstractChapter
* \brief The AbstractChapter class parses chapter information. * \brief The AbstractChapter class parses chapter information.

View File

@ -5,7 +5,6 @@
#include <c++utilities/chrono/timespan.h> #include <c++utilities/chrono/timespan.h>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@ -13,7 +12,6 @@ namespace TagParser {
class AbortableProgressFeedback; class AbortableProgressFeedback;
class Diagnostics; class Diagnostics;
struct AbstractChapterPrivate;
class TAG_PARSER_EXPORT AbstractChapter { class TAG_PARSER_EXPORT AbstractChapter {
public: public:
@ -43,7 +41,6 @@ protected:
CppUtilities::TimeSpan m_startTime; CppUtilities::TimeSpan m_startTime;
CppUtilities::TimeSpan m_endTime; CppUtilities::TimeSpan m_endTime;
std::vector<std::uint64_t> m_tracks; std::vector<std::uint64_t> m_tracks;
std::unique_ptr<AbstractChapterPrivate> m_p;
bool m_hidden; bool m_hidden;
bool m_enabled; bool m_enabled;
}; };

View File

@ -6,11 +6,6 @@ using namespace CppUtilities;
namespace TagParser { namespace TagParser {
/// \brief The AbstractContainerPrivate struct contains private fields of the AbstractContainer class.
struct AbstractContainerPrivate {
std::vector<std::string> muxingApps, writingApps;
};
/*! /*!
* \class TagParser::AbstractContainer * \class TagParser::AbstractContainer
* \brief The AbstractContainer class provides an interface and common functionality to parse and make a certain container format. * \brief The AbstractContainer class provides an interface and common functionality to parse and make a certain container format.
@ -477,40 +472,6 @@ bool AbstractContainer::supportsTitle() const
return false; return false;
} }
/*!
* \brief Returns the muxing applications specified as meta-data.
*/
const std::vector<std::string> &AbstractContainer::muxingApplications() const
{
static const auto empty = std::vector<std::string>();
return m_p ? m_p->muxingApps : empty;
}
/*!
* \brief Returns the muxing applications specified as meta-data.
*/
std::vector<std::string> &AbstractContainer::muxingApplications()
{
return p()->muxingApps;
}
/*!
* \brief Returns the writing applications specified as meta-data.
*/
const std::vector<std::string> &AbstractContainer::writingApplications() const
{
static const auto empty = std::vector<std::string>();
return m_p ? m_p->writingApps : empty;
}
/*!
* \brief Returns the writing applications specified as meta-data.
*/
std::vector<std::string> &AbstractContainer::writingApplications()
{
return p()->writingApps;
}
/*! /*!
* \brief Returns the number of segments. * \brief Returns the number of segments.
*/ */
@ -538,15 +499,4 @@ void AbstractContainer::reset()
m_titles.clear(); m_titles.clear();
} }
/*!
* \brief Returns the private data for the container.
*/
std::unique_ptr<AbstractContainerPrivate> &AbstractContainer::p()
{
if (!m_p) {
m_p = std::make_unique<AbstractContainerPrivate>();
}
return m_p;
}
} // namespace TagParser } // namespace TagParser

View File

@ -11,7 +11,6 @@
#include <c++utilities/io/binarywriter.h> #include <c++utilities/io/binarywriter.h>
#include <iostream> #include <iostream>
#include <memory>
namespace CppUtilities { namespace CppUtilities {
class BinaryReader; class BinaryReader;
@ -26,7 +25,6 @@ class AbstractChapter;
class AbstractAttachment; class AbstractAttachment;
class Diagnostics; class Diagnostics;
class AbortableProgressFeedback; class AbortableProgressFeedback;
struct AbstractContainerPrivate;
class TAG_PARSER_EXPORT AbstractContainer { class TAG_PARSER_EXPORT AbstractContainer {
public: public:
@ -80,8 +78,6 @@ public:
const std::vector<std::string> &titles() const; const std::vector<std::string> &titles() const;
void setTitle(std::string_view title, std::size_t segmentIndex = 0); void setTitle(std::string_view title, std::size_t segmentIndex = 0);
virtual bool supportsTitle() const; virtual bool supportsTitle() const;
const std::vector<std::string> &muxingApplications() const;
const std::vector<std::string> &writingApplications() const;
virtual std::size_t segmentCount() const; virtual std::size_t segmentCount() const;
CppUtilities::TimeSpan duration() const; CppUtilities::TimeSpan duration() const;
CppUtilities::DateTime creationTime() const; CppUtilities::DateTime creationTime() const;
@ -99,8 +95,6 @@ protected:
virtual void internalParseChapters(Diagnostics &diag, AbortableProgressFeedback &progress); virtual void internalParseChapters(Diagnostics &diag, AbortableProgressFeedback &progress);
virtual void internalParseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress); virtual void internalParseAttachments(Diagnostics &diag, AbortableProgressFeedback &progress);
virtual void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress); virtual void internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress);
std::vector<std::string> &muxingApplications();
std::vector<std::string> &writingApplications();
std::uint64_t m_version; std::uint64_t m_version;
std::uint64_t m_readVersion; std::uint64_t m_readVersion;
@ -121,13 +115,10 @@ protected:
bool m_attachmentsParsed; bool m_attachmentsParsed;
private: private:
std::unique_ptr<AbstractContainerPrivate> &p();
std::uint64_t m_startOffset; std::uint64_t m_startOffset;
std::iostream *m_stream; std::iostream *m_stream;
CppUtilities::BinaryReader m_reader; CppUtilities::BinaryReader m_reader;
CppUtilities::BinaryWriter m_writer; CppUtilities::BinaryWriter m_writer;
std::unique_ptr<AbstractContainerPrivate> m_p;
}; };
/*! /*!

View File

@ -11,9 +11,6 @@ using namespace CppUtilities;
namespace TagParser { namespace TagParser {
/// \brief The AbstractTrackPrivate struct contains private fields of the AbstractTrack class.
struct AbstractTrackPrivate {};
/*! /*!
* \class TagParser::AbstractTrack * \class TagParser::AbstractTrack
* \brief The AbstractTrack class parses and stores technical information about * \brief The AbstractTrack class parses and stores technical information about

View File

@ -15,7 +15,6 @@
#include <c++utilities/misc/flagenumclass.h> #include <c++utilities/misc/flagenumclass.h>
#include <iosfwd> #include <iosfwd>
#include <memory>
#include <string> #include <string>
#include <string_view> #include <string_view>
@ -105,8 +104,6 @@ CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(TagParser, TagParser::TrackFlags)
namespace TagParser { namespace TagParser {
struct AbstractTrackPrivate;
class TAG_PARSER_EXPORT AbstractTrack { class TAG_PARSER_EXPORT AbstractTrack {
friend class MpegAudioFrameStream; friend class MpegAudioFrameStream;
friend class WaveAudioStream; friend class WaveAudioStream;
@ -132,7 +129,6 @@ public:
MediaType mediaType() const; MediaType mediaType() const;
std::string_view mediaTypeName() const; std::string_view mediaTypeName() const;
std::uint64_t size() const; std::uint64_t size() const;
void setSize(std::uint64_t size);
std::uint32_t trackNumber() const; std::uint32_t trackNumber() const;
void setTrackNumber(std::uint32_t trackNumber); void setTrackNumber(std::uint32_t trackNumber);
std::uint64_t id() const; std::uint64_t id() const;
@ -236,7 +232,6 @@ protected:
AlphaMode m_alphaMode; AlphaMode m_alphaMode;
DisplayUnit m_displayUnit; DisplayUnit m_displayUnit;
AspectRatioType m_aspectRatioType; AspectRatioType m_aspectRatioType;
std::unique_ptr<AbstractTrackPrivate> m_p;
private: private:
std::string makeDescription(bool verbose) const; std::string makeDescription(bool verbose) const;
@ -397,19 +392,6 @@ inline std::uint64_t AbstractTrack::size() const
return m_size; return m_size;
} }
/*!
* \brief Sets the size in bytes.
* \remarks
* This is used by MediaFileInfo to set the track size for certain types of tracks before invoking the parsing.
* If you use this a class derived from AbstractTrack directly you may want to do the same if not the entire
* input stream is supposed to be considered part of the track and the parser would otherwise assume that (like
* the parser of MpegAudioFrameStream might do).
*/
inline void AbstractTrack::setSize(std::uint64_t size)
{
m_size = size;
}
/*! /*!
* \brief Returns the track number if known; otherwise returns 0. * \brief Returns the track number if known; otherwise returns 0.
*/ */

View File

@ -24,8 +24,15 @@ void AdtsStream::internalParseHeader(Diagnostics &diag, AbortableProgressFeedbac
if (!m_istream) { if (!m_istream) {
throw NoDataFoundException(); throw NoDataFoundException();
} }
// get size
m_istream->seekg(-128, ios_base::end);
if (m_reader.readUInt24BE() == 0x544147) {
m_size = static_cast<std::uint64_t>(m_istream->tellg()) - 3u - m_startOffset;
} else {
m_size = static_cast<std::uint64_t>(m_istream->tellg()) + 125u - m_startOffset;
}
m_istream->seekg(static_cast<streamoff>(m_startOffset), ios_base::beg);
// parse frame header // parse frame header
m_istream->seekg(static_cast<std::streamoff>(m_startOffset), ios_base::beg);
m_firstFrame.parseHeader(m_reader); m_firstFrame.parseHeader(m_reader);
m_format = Mpeg4AudioObjectIds::idToMediaFormat(m_firstFrame.mpeg4AudioObjectId()); m_format = Mpeg4AudioObjectIds::idToMediaFormat(m_firstFrame.mpeg4AudioObjectId());
m_channelCount = Mpeg4ChannelConfigs::channelCount(m_channelConfig = m_firstFrame.mpeg4ChannelConfig()); m_channelCount = Mpeg4ChannelConfigs::channelCount(m_channelConfig = m_firstFrame.mpeg4ChannelConfig());

View File

@ -42,13 +42,12 @@ void example()
// code. // code.
// - Parsing a file can be expensive if the file is big or the disk IO is slow. You might want to // - Parsing a file can be expensive if the file is big or the disk IO is slow. You might want to
// run it in a separate thread. // run it in a separate thread.
// - At this point the parser does not make much use of the progress object (in contrast to applyChanges() // - At this point the parser does not make much use of the progress object.
// shown below).
fileInfo.parseContainerFormat(diag, progress); fileInfo.parseContainerFormat(diag, progress);
fileInfo.parseTags(diag, progress); fileInfo.parseTags(diag, progress);
fileInfo.parseAttachments(diag, progress); fileInfo.parseAttachments(diag, progress);
fileInfo.parseChapters(diag, progress); fileInfo.parseChapters(diag, progress);
fileInfo.parseEverything(diag, progress); // just use that one if you want all of the above fileInfo.parseEverything(diag, progress); // just use that one if you want all over the above
// get tag as an object derived from the Tag class // get tag as an object derived from the Tag class
// notes: // notes:
@ -56,7 +55,7 @@ void example()
// fileInfo.createAppropriateTags(…) to create tags as needed. // fileInfo.createAppropriateTags(…) to create tags as needed.
auto tag = fileInfo.tags().at(0); auto tag = fileInfo.tags().at(0);
// extract a field value and convert it to a UTF-8 std::string (toString() might throw ConversionException) // extract a field value and convert it to UTF-8 std::string (toString() might throw ConversionException)
auto title = tag->value(TagParser::KnownField::Title).toString(TagParser::TagTextEncoding::Utf8); auto title = tag->value(TagParser::KnownField::Title).toString(TagParser::TagTextEncoding::Utf8);
// change a field value using an encoding suitable for the tag format // change a field value using an encoding suitable for the tag format
@ -85,5 +84,6 @@ void example()
// - Use progress.tryToAbort() from another thread or an interrupt handler to abort gracefully without leaving // - Use progress.tryToAbort() from another thread or an interrupt handler to abort gracefully without leaving
// the file in an inconsistent state. // the file in an inconsistent state.
// - Be sure everything has been parsed before as the library needs to be aware of the whole file structure. // - Be sure everything has been parsed before as the library needs to be aware of the whole file structure.
fileInfo.parseEverything(diag, progress);
fileInfo.applyChanges(diag, progress); fileInfo.applyChanges(diag, progress);
} }

View File

@ -115,11 +115,7 @@ void FlacStream::internalParseHeader(Diagnostics &diag, AbortableProgressFeedbac
m_vorbisComment = make_unique<VorbisComment>(); m_vorbisComment = make_unique<VorbisComment>();
} }
try { try {
auto flags = VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte; m_vorbisComment->parse(*m_istream, header.dataSize(), VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, diag);
if (m_mediaFileInfo.fileHandlingFlags() & MediaFileHandlingFlags::ConvertTotalFields) {
flags += VorbisCommentFlags::ConvertTotalFields;
}
m_vorbisComment->parse(*m_istream, header.dataSize(), flags, diag);
} catch (const Failure &) { } catch (const Failure &) {
// error is logged via notifications, just continue with the next metadata block // error is logged via notifications, just continue with the next metadata block
} }

View File

@ -27,7 +27,7 @@ void FlacToOggMappingHeader::parseHeader(OggIterator &iterator)
constexpr auto idSize = 0x05, mappingHeaderSize = 0x0D, blockHeaderSize = 0x04, streamInfoSize = 0x22; constexpr auto idSize = 0x05, mappingHeaderSize = 0x0D, blockHeaderSize = 0x04, streamInfoSize = 0x22;
char buff[mappingHeaderSize + blockHeaderSize + streamInfoSize - idSize]; char buff[mappingHeaderSize + blockHeaderSize + streamInfoSize - idSize];
iterator.read(buff, idSize); iterator.read(buff, idSize);
if (*buff != 0x7Fu || BE::toInt<std::uint32_t>(buff + 1) != 0x464C4143u) { if (*buff != 0x7Fu || BE::toUInt32(buff + 1) != 0x464C4143u) {
throw InvalidDataException(); // not FLAC-to-Ogg mapping header throw InvalidDataException(); // not FLAC-to-Ogg mapping header
} }
iterator.read(buff, sizeof(buff)); iterator.read(buff, sizeof(buff));
@ -35,8 +35,8 @@ void FlacToOggMappingHeader::parseHeader(OggIterator &iterator)
// parse FLAC-to-Ogg mapping header // parse FLAC-to-Ogg mapping header
m_majorVersion = static_cast<std::uint8_t>(*(buff + 0x00)); m_majorVersion = static_cast<std::uint8_t>(*(buff + 0x00));
m_minorVersion = static_cast<std::uint8_t>(*(buff + 0x01)); m_minorVersion = static_cast<std::uint8_t>(*(buff + 0x01));
m_headerCount = BE::toInt<std::uint16_t>(buff + 0x02); m_headerCount = BE::toUInt16(buff + 0x02);
if (BE::toInt<std::uint32_t>(buff + 0x04) != 0x664C6143u) { if (BE::toUInt32(buff + 0x04) != 0x664C6143u) {
throw InvalidDataException(); // native FLAC signature not present throw InvalidDataException(); // native FLAC signature not present
} }

View File

@ -944,7 +944,7 @@ void GenericFileElement<ImplementationType>::copyInternal(
} catch (const Failure &) { } catch (const Failure &) {
throw InvalidDataException(); throw InvalidDataException();
} }
auto &stream = container().stream(); auto &stream = container().fileInfo().stream();
stream.seekg(static_cast<std::streamoff>(startOffset), std::ios_base::beg); stream.seekg(static_cast<std::streamoff>(startOffset), std::ios_base::beg);
CppUtilities::CopyHelper<0x10000> copyHelper; CppUtilities::CopyHelper<0x10000> copyHelper;
if (progress) { if (progress) {

View File

@ -4,7 +4,6 @@
#ifndef TAG_PARSER_GLOBAL #ifndef TAG_PARSER_GLOBAL
#define TAG_PARSER_GLOBAL #define TAG_PARSER_GLOBAL
#include "tagparser-definitions.h"
#include <c++utilities/application/global.h> #include <c++utilities/application/global.h>
#ifdef TAG_PARSER_STATIC #ifdef TAG_PARSER_STATIC

View File

@ -934,7 +934,7 @@ tuple<const char *, size_t, const char *> Id3v2Frame::parseSubstring(
case TagTextEncoding::Utf16BigEndian: case TagTextEncoding::Utf16BigEndian:
case TagTextEncoding::Utf16LittleEndian: { case TagTextEncoding::Utf16LittleEndian: {
if (bufferSize >= 2) { if (bufferSize >= 2) {
switch (LE::toInt<std::uint16_t>(buffer)) { switch (LE::toUInt16(buffer)) {
case 0xFEFF: case 0xFEFF:
if (encoding == TagTextEncoding::Utf16BigEndian) { if (encoding == TagTextEncoding::Utf16BigEndian) {
diag.emplace_back(DiagLevel::Critical, diag.emplace_back(DiagLevel::Critical,
@ -1002,9 +1002,9 @@ void Id3v2Frame::parseBom(const char *buffer, std::size_t maxSize, TagTextEncodi
switch (encoding) { switch (encoding) {
case TagTextEncoding::Utf16BigEndian: case TagTextEncoding::Utf16BigEndian:
case TagTextEncoding::Utf16LittleEndian: case TagTextEncoding::Utf16LittleEndian:
if ((maxSize >= 2) && (BE::toInt<std::uint16_t>(buffer) == 0xFFFE)) { if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
encoding = TagTextEncoding::Utf16LittleEndian; encoding = TagTextEncoding::Utf16LittleEndian;
} else if ((maxSize >= 2) && (BE::toInt<std::uint16_t>(buffer) == 0xFEFF)) { } else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
encoding = TagTextEncoding::Utf16BigEndian; encoding = TagTextEncoding::Utf16BigEndian;
} }
break; break;
@ -1172,7 +1172,7 @@ void Id3v2Frame::makeLegacyPicture(
} else { } else {
imageFormat = "UND"; imageFormat = "UND";
} }
std::memcpy(++offset, imageFormat, 3); strncpy(++offset, imageFormat, 3);
// write picture type // write picture type
*(offset += 3) = static_cast<char>(typeInfo); *(offset += 3) = static_cast<char>(typeInfo);

View File

@ -338,7 +338,7 @@ inline Id3v2Frame::IdentifierType Id3v2Frame::fieldIdFromString(std::string_view
case 3: case 3:
return CppUtilities::BE::toUInt24(idString.data()); return CppUtilities::BE::toUInt24(idString.data());
case 4: case 4:
return CppUtilities::BE::toInt<std::uint32_t>(idString.data()); return CppUtilities::BE::toUInt32(idString.data());
default: default:
throw CppUtilities::ConversionException("ID3v2 ID must be 3 or 4 chars"); throw CppUtilities::ConversionException("ID3v2 ID must be 3 or 4 chars");
} }

View File

@ -85,10 +85,6 @@ std::uint32_t convertToShortId(std::uint32_t id)
return sRating; return sRating;
case lISRC: case lISRC:
return sISRC; return sISRC;
case lPublisherWebpage:
return sPublisherWebpage;
case lUserDefinedURL:
return sUserDefinedURL;
default: default:
return 0; return 0;
} }
@ -159,10 +155,6 @@ std::uint32_t convertToLongId(std::uint32_t id)
return lRating; return lRating;
case sISRC: case sISRC:
return lISRC; return lISRC;
case sPublisherWebpage:
return lPublisherWebpage;
case sUserDefinedURL:
return lUserDefinedURL;
default: default:
return 0; return 0;
} }

View File

@ -49,8 +49,6 @@ enum KnownValue : std::uint32_t {
lMood = 0x544D4F4F, /**< TMOO */ lMood = 0x544D4F4F, /**< TMOO */
lISRC = 0x54535243, /**< TSRC */ lISRC = 0x54535243, /**< TSRC */
lUserDefinedText = 0x54585858, /**< TXXX */ lUserDefinedText = 0x54585858, /**< TXXX */
lPublisherWebpage = 0x57505542, /**< WPUB */
lUserDefinedURL = 0x57585858, /**< WXXX */
sAlbum = 0x54414c, /**< ?TAL */ sAlbum = 0x54414c, /**< ?TAL */
sArtist = 0x545031, /**< ?TP1 */ sArtist = 0x545031, /**< ?TP1 */
@ -84,8 +82,6 @@ enum KnownValue : std::uint32_t {
sCopyright = 0x544352, /**< TCR */ sCopyright = 0x544352, /**< TCR */
sISRC = 0x545243, /**< TRC */ sISRC = 0x545243, /**< TRC */
sUserDefinedText = 0x545858, /**< ?TXX */ sUserDefinedText = 0x545858, /**< ?TXX */
sPublisherWebpage = 0x575042, /**< ?WPB */
sUserDefinedURL = 0x575858, /**< ?WXX */
}; };
TAG_PARSER_EXPORT std::uint32_t convertToShortId(std::uint32_t id); TAG_PARSER_EXPORT std::uint32_t convertToShortId(std::uint32_t id);
@ -121,14 +117,6 @@ constexpr bool isTextFrame(std::uint32_t id)
} }
} }
/*!
* \brief Returns an indication whether the specified \a id is a URL frame id.
*/
constexpr bool isUrlFrame(std::uint32_t id)
{
return (id & 0xFF000000u) == 0x57000000u && (id != Id3v2FrameIds::lUserDefinedURL);
}
} // namespace Id3v2FrameIds } // namespace Id3v2FrameIds
} // namespace TagParser } // namespace TagParser

View File

@ -16,7 +16,7 @@ namespace TagParser {
/*! /*!
* \class TagParser::IvfStream * \class TagParser::IvfStream
* \brief Implementation of TagParser::AbstractTrack for IVF streams. * \brief Implementation of TagParser::AbstractTrack for ADTS streams.
* \sa https://wiki.multimedia.cx/index.php/IVF * \sa https://wiki.multimedia.cx/index.php/IVF
*/ */

View File

@ -100,7 +100,7 @@ void EbmlElement::internalParse(Diagnostics &diag)
continue; // try again continue; // try again
} }
reader().read(buf + (maximumIdLengthSupported() - m_idLength), m_idLength); reader().read(buf + (maximumIdLengthSupported() - m_idLength), m_idLength);
m_id = BE::toInt<std::uint32_t>(buf); m_id = BE::toUInt32(buf);
// check whether this element is actually a sibling of one of its parents rather then a child // check whether this element is actually a sibling of one of its parents rather then a child
// (might be the case if the parent's size is unknown and hence assumed to be the max file size) // (might be the case if the parent's size is unknown and hence assumed to be the max file size)
@ -172,7 +172,7 @@ void EbmlElement::internalParse(Diagnostics &diag)
reader().read(buf + (maximumSizeLengthSupported() - m_sizeLength), m_sizeLength); reader().read(buf + (maximumSizeLengthSupported() - m_sizeLength), m_sizeLength);
// xor the first byte in buffer which has been read from the file with mask // xor the first byte in buffer which has been read from the file with mask
*(buf + (maximumSizeLengthSupported() - m_sizeLength)) ^= static_cast<char>(mask); *(buf + (maximumSizeLengthSupported() - m_sizeLength)) ^= static_cast<char>(mask);
m_dataSize = BE::toInt<std::uint64_t>(buf); m_dataSize = BE::toUInt64(buf);
// check if element is truncated // check if element is truncated
if (totalSize() > maxTotalSize()) { if (totalSize() > maxTotalSize()) {
if (m_idLength + m_sizeLength > maxTotalSize()) { // header truncated if (m_idLength + m_sizeLength > maxTotalSize()) { // header truncated
@ -242,7 +242,7 @@ std::uint64_t EbmlElement::readUInteger()
const auto bytesToSkip = maxBytesToRead - min(dataSize(), maxBytesToRead); const auto bytesToSkip = maxBytesToRead - min(dataSize(), maxBytesToRead);
stream().seekg(static_cast<streamoff>(dataOffset()), ios_base::beg); stream().seekg(static_cast<streamoff>(dataOffset()), ios_base::beg);
stream().read(buff + bytesToSkip, static_cast<streamoff>(sizeof(buff) - bytesToSkip)); stream().read(buff + bytesToSkip, static_cast<streamoff>(sizeof(buff) - bytesToSkip));
return BE::toInt<std::uint64_t>(buff); return BE::toUInt64(buff);
} }
/*! /*!

View File

@ -35,6 +35,8 @@ namespace TagParser {
* \brief Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>. * \brief Implementation of GenericContainer<MediaFileInfo, MatroskaTag, MatroskaTrack, EbmlElement>.
*/ */
std::uint64_t MatroskaContainer::m_maxFullParseSize = 0x3200000; // FIXME v11: move to MediaFileInfo
/*! /*!
* \brief Constructs a new container for the specified \a fileInfo at the specified \a startOffset. * \brief Constructs a new container for the specified \a fileInfo at the specified \a startOffset.
*/ */
@ -82,13 +84,13 @@ void MatroskaContainer::reset()
*/ */
void MatroskaContainer::validateIndex(Diagnostics &diag, AbortableProgressFeedback &progress) void MatroskaContainer::validateIndex(Diagnostics &diag, AbortableProgressFeedback &progress)
{ {
static const auto context = std::string("validating Matroska file index (cues)"); static const string context("validating Matroska file index (cues)");
auto cuesElementsFound = false; bool cuesElementsFound = false;
if (m_firstElement) { if (m_firstElement) {
auto ids = std::unordered_set<EbmlElement::IdentifierType>(); unordered_set<EbmlElement::IdentifierType> ids;
auto cueTimeFound = false, cueTrackPositionsFound = false; bool cueTimeFound = false, cueTrackPositionsFound = false;
auto clusterElement = std::unique_ptr<EbmlElement>(); unique_ptr<EbmlElement> clusterElement;
auto pos = std::uint64_t(), prevClusterSize = std::uint64_t(), currentOffset = std::uint64_t(); std::uint64_t pos, prevClusterSize = 0, currentOffset = 0;
// iterate through all segments // iterate through all segments
for (EbmlElement *segmentElement = m_firstElement->siblingById(MatroskaIds::Segment, diag); segmentElement; for (EbmlElement *segmentElement = m_firstElement->siblingById(MatroskaIds::Segment, diag); segmentElement;
segmentElement = segmentElement->siblingById(MatroskaIds::Segment, diag)) { segmentElement = segmentElement->siblingById(MatroskaIds::Segment, diag)) {
@ -562,7 +564,7 @@ void MatroskaContainer::internalParseHeader(Diagnostics &diag, AbortableProgress
} }
} }
// -> stop if tracks and tags have been found or the file exceeds the max. size to fully process // -> stop if tracks and tags have been found or the file exceeds the max. size to fully process
if (((!m_tracksElements.empty() && !m_tagsElements.empty()) || fileInfo().size() > fileInfo().maxFullParseSize()) if (((!m_tracksElements.empty() && !m_tagsElements.empty()) || fileInfo().size() > m_maxFullParseSize)
&& !m_segmentInfoElements.empty()) { && !m_segmentInfoElements.empty()) {
goto finish; goto finish;
} }
@ -627,12 +629,6 @@ void MatroskaContainer::parseSegmentInfo(Diagnostics &diag)
case MatroskaIds::TimeCodeScale: case MatroskaIds::TimeCodeScale:
timeScale = subElement->readUInteger(); timeScale = subElement->readUInteger();
break; break;
case MatroskaIds::MuxingApp:
muxingApplications().emplace_back(subElement->readString());
break;
case MatroskaIds::WrittingApp:
writingApplications().emplace_back(subElement->readString());
break;
} }
subElement = subElement->nextSibling(); subElement = subElement->nextSibling();
} }
@ -838,7 +834,7 @@ struct SegmentData {
MatroskaCuePositionUpdater cuesUpdater; MatroskaCuePositionUpdater cuesUpdater;
/// \brief size of the "SegmentInfo"-element /// \brief size of the "SegmentInfo"-element
std::uint64_t infoDataSize; std::uint64_t infoDataSize;
/// \brief cluster sizes, needed because cluster elements are not necessarily copied as-is so they're size might change /// \brief cluster sizes
std::vector<std::uint64_t> clusterSizes; std::vector<std::uint64_t> clusterSizes;
/// \brief first "Cluster"-element (original file) /// \brief first "Cluster"-element (original file)
EbmlElement *firstClusterElement; EbmlElement *firstClusterElement;
@ -948,18 +944,13 @@ void MatroskaContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFee
const std::uint64_t ebmlHeaderSize = 4 + EbmlElement::calculateSizeDenotationLength(ebmlHeaderDataSize) + ebmlHeaderDataSize; const std::uint64_t ebmlHeaderSize = 4 + EbmlElement::calculateSizeDenotationLength(ebmlHeaderDataSize) + ebmlHeaderDataSize;
// calculate size of "WritingLib"-element // calculate size of "WritingLib"-element
const auto &muxingApps = const_cast<const MatroskaContainer *>(this)->muxingApplications(); constexpr std::string_view muxingAppName = APP_NAME " v" APP_VERSION;
const auto muxingAppName = (fileInfo().fileHandlingFlags() & MediaFileHandlingFlags::PreserveMuxingApplication && !muxingApps.empty()) constexpr std::uint64_t muxingAppElementTotalSize = 2 + 1 + muxingAppName.size();
? std::string_view(muxingApps.front())
: std::string_view(APP_NAME " v" APP_VERSION);
const auto muxingAppElementTotalSize = std::uint64_t(2 + 1 + muxingAppName.size());
// calculate size of "WritingApp"-element // calculate size of "WritingApp"-element
const auto writingApps = const_cast<const MatroskaContainer *>(this)->writingApplications(); const std::uint64_t writingAppElementDataSize
const auto writingAppName = (fileInfo().fileHandlingFlags() & MediaFileHandlingFlags::PreserveWritingApplication && !writingApps.empty()) = fileInfo().writingApplication().empty() ? muxingAppName.size() : fileInfo().writingApplication().size();
? std::string_view(writingApps.front()) const std::uint64_t writingAppElementTotalSize = 2 + 1 + writingAppElementDataSize;
: std::string_view(fileInfo().writingApplication().empty() ? muxingAppName : std::string_view(fileInfo().writingApplication()));
const auto writingAppElementTotalSize = std::uint64_t(2 + 1 + writingAppName.size());
try { try {
// calculate size of "Tags"-element // calculate size of "Tags"-element
@ -1228,7 +1219,7 @@ void MatroskaContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFee
progress.updateStep("Calculating cluster offsets ..."); progress.updateStep("Calculating cluster offsets ...");
} }
// decide whether it is necessary to rewrite the entire file (if not already rewriting) // decided whether it is necessary to rewrite the entire file (if not already rewriting)
if (!rewriteRequired) { if (!rewriteRequired) {
// find first "Cluster"-element // find first "Cluster"-element
if ((level1Element = segment.firstClusterElement)) { if ((level1Element = segment.firstClusterElement)) {
@ -1366,7 +1357,7 @@ void MatroskaContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFee
goto calculateSegmentData; goto calculateSegmentData;
} }
} else { } else {
// if rewrite is required, pretend writing the remaining elements to compute total segment size and cluster sizes // if rewrite is required, pretend writing the remaining elements to compute total segment size
// pretend writing "Void"-element (only if there is at least one "Cluster"-element in the segment) // pretend writing "Void"-element (only if there is at least one "Cluster"-element in the segment)
if (!segmentIndex && rewriteRequired && (level1Element = level0Element->childById(MatroskaIds::Cluster, diag))) { if (!segmentIndex && rewriteRequired && (level1Element = level0Element->childById(MatroskaIds::Cluster, diag))) {
@ -1648,7 +1639,8 @@ void MatroskaContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFee
} }
// -> write "MuxingApp"- and "WritingApp"-element // -> write "MuxingApp"- and "WritingApp"-element
EbmlElement::makeSimpleElement(outputStream, MatroskaIds::MuxingApp, muxingAppName); EbmlElement::makeSimpleElement(outputStream, MatroskaIds::MuxingApp, muxingAppName);
EbmlElement::makeSimpleElement(outputStream, MatroskaIds::WrittingApp, writingAppName); EbmlElement::makeSimpleElement(outputStream, MatroskaIds::WrittingApp,
fileInfo().writingApplication().empty() ? muxingAppName : fileInfo().writingApplication());
} }
// write "Tracks"-element // write "Tracks"-element

View File

@ -31,6 +31,8 @@ public:
std::uint64_t maxSizeLength() const; std::uint64_t maxSizeLength() const;
const std::vector<std::unique_ptr<MatroskaSeekInfo>> &seekInfos() const; const std::vector<std::unique_ptr<MatroskaSeekInfo>> &seekInfos() const;
static std::uint64_t maxFullParseSize();
void setMaxFullParseSize(std::uint64_t maxFullParseSize);
const std::vector<std::unique_ptr<MatroskaEditionEntry>> &editionEntires() const; const std::vector<std::unique_ptr<MatroskaEditionEntry>> &editionEntires() const;
MatroskaChapter *chapter(std::size_t index) override; MatroskaChapter *chapter(std::size_t index) override;
std::size_t chapterCount() const override; std::size_t chapterCount() const override;
@ -70,6 +72,7 @@ private:
std::vector<std::unique_ptr<MatroskaEditionEntry>> m_editionEntries; std::vector<std::unique_ptr<MatroskaEditionEntry>> m_editionEntries;
std::vector<std::unique_ptr<MatroskaAttachment>> m_attachments; std::vector<std::unique_ptr<MatroskaAttachment>> m_attachments;
std::size_t m_segmentCount; std::size_t m_segmentCount;
static std::uint64_t m_maxFullParseSize;
}; };
/*! /*!
@ -96,6 +99,33 @@ inline const std::vector<std::unique_ptr<MatroskaSeekInfo>> &MatroskaContainer::
return m_seekInfos; return m_seekInfos;
} }
/*!
* \brief Returns the maximal file size for a "full parse" in byte.
*
* The "Tags" element (which holds the tag information) is commonly at the end of a Matroska file. Hence the
* parser needs to walk through the entire file to find the tag information if no "SeekHead" element is present
* which might causes long loading times. To avoid this a maximal file size for a "full parse" can be specified.
* The disadvantage is that the parser relies on the presence of a SeekHead element on larger files to retrieve
* tag information.
*
* The default value is 50 MiB.
*
* \sa setMaxFullParseSize()
*/
inline std::uint64_t MatroskaContainer::maxFullParseSize()
{
return m_maxFullParseSize;
}
/*!
* \brief Sets the maximal file size for a "full parse" in byte.
* \sa maxFullParseSize()
*/
inline void MatroskaContainer::setMaxFullParseSize(std::uint64_t maxFullParseSize)
{
m_maxFullParseSize = maxFullParseSize;
}
/*! /*!
* \brief Returns the edition entries. * \brief Returns the edition entries.
*/ */

View File

@ -78,7 +78,7 @@ void MatroskaCuePositionUpdater::parse(EbmlElement *cuesElement, Diagnostics &di
cuePointElementSize += cuePointChild->totalSize(); cuePointElementSize += cuePointChild->totalSize();
break; break;
case MatroskaIds::CueTrackPositions: case MatroskaIds::CueTrackPositions:
cueTrackPositionsElementSize = relPos = 0; cueTrackPositionsElementSize = 0;
cueRelativePositionElement = cueClusterPositionElement = nullptr; cueRelativePositionElement = cueClusterPositionElement = nullptr;
for (EbmlElement *cueTrackPositionsChild = cuePointChild->firstChild(); cueTrackPositionsChild; for (EbmlElement *cueTrackPositionsChild = cuePointChild->firstChild(); cueTrackPositionsChild;
cueTrackPositionsChild = cueTrackPositionsChild->nextSibling()) { cueTrackPositionsChild = cueTrackPositionsChild->nextSibling()) {

View File

@ -122,7 +122,7 @@ std::string_view matroskaIdName(std::uint32_t matroskaId)
return "track number"; return "track number";
case TrackUID: case TrackUID:
return "unique track id"; return "unique track id";
case TrackEntryIds::TrackType: case TrackType:
return "track type"; return "track type";
case TrackAudio: case TrackAudio:
return "audio track"; return "audio track";
@ -192,7 +192,7 @@ std::string_view matroskaIdName(std::uint32_t matroskaId)
return "video display width"; return "video display width";
case DisplayHeight: case DisplayHeight:
return "video display height"; return "video display height";
case TrackVideoIds::DisplayUnit: case DisplayUnit:
return "video display unit"; return "video display unit";
case PixelWidth: case PixelWidth:
return "video pixel width"; return "video pixel width";
@ -208,9 +208,9 @@ std::string_view matroskaIdName(std::uint32_t matroskaId)
return "video pixel crop right"; return "video pixel crop right";
case FlagInterlaced: case FlagInterlaced:
return "video flag interlaced"; return "video flag interlaced";
case TrackVideoIds::StereoMode: case StereoMode:
return "video stereo mode"; return "video stereo mode";
case TrackVideoIds::AspectRatioType: case AspectRatioType:
return "video aspect ratio type"; return "video aspect ratio type";
case ColorSpace: case ColorSpace:
return "video color space"; return "video color space";
@ -276,7 +276,7 @@ std::string_view matroskaIdName(std::uint32_t matroskaId)
return "content encryption signature hash algorithmus"; return "content encryption signature hash algorithmus";
// IDs in the Tags master // IDs in the Tags master
case TagsIds::Tag: case Tag:
return "tag"; return "tag";
// IDs in the Tag master // IDs in the Tag master
@ -569,7 +569,7 @@ MatroskaElementLevel matroskaIdLevel(std::uint32_t matroskaId)
case CuePoint: case CuePoint:
case AttachedFile: case AttachedFile:
case EditionEntry: case EditionEntry:
case TagsIds::Tag: case Tag:
return MatroskaElementLevel::Level2; return MatroskaElementLevel::Level2;
case SeekID: case SeekID:
case SeekPosition: case SeekPosition:
@ -588,7 +588,7 @@ MatroskaElementLevel matroskaIdLevel(std::uint32_t matroskaId)
case Slices: case Slices:
case TrackNumber: case TrackNumber:
case TrackUID: case TrackUID:
case TrackEntryIds::TrackType: case TrackType:
case TrackFlagEnabled: case TrackFlagEnabled:
case TrackFlagDefault: case TrackFlagDefault:
case TrackFlagForced: case TrackFlagForced:

View File

@ -43,7 +43,7 @@ enum SeekIds { SeekID = 0x53AB, SeekPosition = 0x53AC };
enum SegmentInfoIds { enum SegmentInfoIds {
TimeCodeScale = 0x2AD7B1, TimeCodeScale = 0x2AD7B1,
Duration = 0x4489, Duration = 0x4489,
WrittingApp = 0x5741, // TODOv13: change to WritingApp WrittingApp = 0x5741,
MuxingApp = 0x4D80, MuxingApp = 0x4D80,
DateUTC = 0x4461, DateUTC = 0x4461,
SegmentUID = 0x73A4, SegmentUID = 0x73A4,

View File

@ -40,7 +40,6 @@
#include <c++utilities/io/path.h> #include <c++utilities/io/path.h>
#include <algorithm> #include <algorithm>
#include <cstdint>
#include <cstdio> #include <cstdio>
#include <filesystem> #include <filesystem>
#include <functional> #include <functional>
@ -60,9 +59,6 @@ using namespace CppUtilities;
namespace TagParser { namespace TagParser {
/// \brief The MediaFileInfoPrivate struct contains private fields of the MediaFileInfo class.
struct MediaFileInfoPrivate {};
/*! /*!
* \class TagParser::MediaFileInfo * \class TagParser::MediaFileInfo
* \brief The MediaFileInfo class allows to read and write tag information providing * \brief The MediaFileInfo class allows to read and write tag information providing
@ -84,7 +80,6 @@ MediaFileInfo::MediaFileInfo(std::string &&path)
, m_containerFormat(ContainerFormat::Unknown) , m_containerFormat(ContainerFormat::Unknown)
, m_containerOffset(0) , m_containerOffset(0)
, m_paddingSize(0) , m_paddingSize(0)
, m_effectiveSize(0)
, m_fileStructureFlags(MediaFileStructureFlags::None) , m_fileStructureFlags(MediaFileStructureFlags::None)
, m_tracksParsingStatus(ParsingStatus::NotParsedYet) , m_tracksParsingStatus(ParsingStatus::NotParsedYet)
, m_tagsParsingStatus(ParsingStatus::NotParsedYet) , m_tagsParsingStatus(ParsingStatus::NotParsedYet)
@ -97,7 +92,6 @@ MediaFileInfo::MediaFileInfo(std::string &&path)
, m_indexPosition(ElementPosition::BeforeData) , m_indexPosition(ElementPosition::BeforeData)
, m_fileHandlingFlags(MediaFileHandlingFlags::ForceRewrite | MediaFileHandlingFlags::ForceTagPosition | MediaFileHandlingFlags::ForceIndexPosition , m_fileHandlingFlags(MediaFileHandlingFlags::ForceRewrite | MediaFileHandlingFlags::ForceTagPosition | MediaFileHandlingFlags::ForceIndexPosition
| MediaFileHandlingFlags::NormalizeKnownTagFieldIds | MediaFileHandlingFlags::PreserveRawTimingValues) | MediaFileHandlingFlags::NormalizeKnownTagFieldIds | MediaFileHandlingFlags::PreserveRawTimingValues)
, m_maxFullParseSize(0x3200000)
{ {
} }
@ -195,7 +189,7 @@ startParsingSignature:
// parse signature // parse signature
switch ((m_containerFormat = parseSignature(buff, sizeof(buff)))) { switch ((m_containerFormat = parseSignature(buff, sizeof(buff)))) {
case ContainerFormat::Id3v2Tag: case ContainerFormat::Id2v2Tag:
// save position of ID3v2 tag // save position of ID3v2 tag
m_actualId3v2TagOffsets.push_back(m_containerOffset); m_actualId3v2TagOffsets.push_back(m_containerOffset);
if (m_actualId3v2TagOffsets.size() == 2) { if (m_actualId3v2TagOffsets.size() == 2) {
@ -207,7 +201,7 @@ startParsingSignature:
stream().read(buff, 5); stream().read(buff, 5);
// set the container offset to skip ID3v2 header // set the container offset to skip ID3v2 header
m_containerOffset += toNormalInt(BE::toInt<std::uint32_t>(buff + 1)) + 10; m_containerOffset += toNormalInt(BE::toUInt32(buff + 1)) + 10;
if ((*buff) & 0x10) { if ((*buff) & 0x10) {
// footer present // footer present
m_containerOffset += 10; m_containerOffset += 10;
@ -260,23 +254,6 @@ startParsingSignature:
static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(isForcingFullParse()); static_cast<OggContainer *>(m_container.get())->setChecksumValidationEnabled(isForcingFullParse());
break; break;
case ContainerFormat::Unknown: case ContainerFormat::Unknown:
case ContainerFormat::ApeTag:
// skip APE tag if the specified size makes sense at all
if (m_containerFormat == ContainerFormat::ApeTag) {
if (const auto apeEnd = m_containerOffset + 32 + LE::toUInt32(buff + 12); apeEnd <= static_cast<std::streamoff>(size())) {
// take record of APE tag
diag.emplace_back(DiagLevel::Critical,
argsToString("Found an APE tag at the beginning of the file at offset ", m_containerOffset,
". This tag format is not supported and the tag will therefore be ignored. It will NOT be preserved when saving as "
"placing an APE tag at the beginning of a file is strongly unrecommended."),
context);
// continue reading signature
m_containerOffset = apeEnd;
goto startParsingSignature;
}
m_containerFormat = ContainerFormat::Unknown;
}
// check for magic numbers at odd offsets // check for magic numbers at odd offsets
// -> check for tar (magic number at offset 0x101) // -> check for tar (magic number at offset 0x101)
if (size() > 0x107) { if (size() > 0x107) {
@ -357,13 +334,6 @@ void MediaFileInfo::parseTracks(Diagnostics &diag, AbortableProgressFeedback &pr
default: default:
throw NotImplementedException(); throw NotImplementedException();
} }
if (m_containerFormat != ContainerFormat::Flac) {
// ensure the effective size has been determined
// note: This is not required for FLAC and should also be avoided as parseTags() will invoke
// parseTracks() when dealing with FLAC files.
parseTags(diag, progress);
m_singleTrack->setSize(m_effectiveSize);
}
m_singleTrack->parseHeader(diag, progress); m_singleTrack->parseHeader(diag, progress);
// take padding for some "single-track" formats into account // take padding for some "single-track" formats into account
@ -408,14 +378,12 @@ void MediaFileInfo::parseTags(Diagnostics &diag, AbortableProgressFeedback &prog
static const string context("parsing tag"); static const string context("parsing tag");
// check for ID3v1 tag // check for ID3v1 tag
auto effectiveSize = static_cast<std::streamoff>(size()); if (size() >= 128) {
if (effectiveSize >= 128) {
m_id3v1Tag = make_unique<Id3v1Tag>(); m_id3v1Tag = make_unique<Id3v1Tag>();
try { try {
stream().seekg(effectiveSize - 128, std::ios_base::beg); stream().seekg(-128, ios_base::end);
m_id3v1Tag->parse(stream(), diag); m_id3v1Tag->parse(stream(), diag);
m_fileStructureFlags += MediaFileStructureFlags::ActualExistingId3v1Tag; m_fileStructureFlags += MediaFileStructureFlags::ActualExistingId3v1Tag;
effectiveSize -= 128;
} catch (const NoDataFoundException &) { } catch (const NoDataFoundException &) {
m_id3v1Tag.reset(); m_id3v1Tag.reset();
} catch (const OperationAbortedException &) { } catch (const OperationAbortedException &) {
@ -427,31 +395,6 @@ void MediaFileInfo::parseTags(Diagnostics &diag, AbortableProgressFeedback &prog
} }
} }
// check for APE tag at the end of the file (APE tags a the beginning are already covered when parsing the container format)
if (constexpr auto apeHeaderSize = 32; effectiveSize >= apeHeaderSize) {
const auto footerOffset = effectiveSize - apeHeaderSize;
char buffer[apeHeaderSize];
stream().seekg(footerOffset, std::ios_base::beg);
stream().read(buffer, sizeof(buffer));
if (BE::toInt<std::uint64_t>(buffer) == 0x4150455441474558ul /* APETAGEX */) {
// take record of APE tag
const auto tagSize = static_cast<std::streamoff>(LE::toInt<std::uint32_t>(buffer + 12));
const auto flags = LE::toInt<std::uint32_t>(buffer + 20);
// subtract tag size (footer size and contents) from effective size
if (tagSize <= effectiveSize) {
effectiveSize -= tagSize;
}
// subtract header size (not included in tag size) from effective size if flags indicate presence of header
if ((flags & 0x80000000u) && (apeHeaderSize <= effectiveSize)) {
effectiveSize -= apeHeaderSize;
}
diag.emplace_back(DiagLevel::Warning,
argsToString("Found an APE tag at the end of the file at offset ", (footerOffset - tagSize),
". This tag format is not supported and the tag will therefore be ignored. It will be preserved when saving as-is."),
context);
}
}
// check for ID3v2 tags: the offsets of the ID3v2 tags have already been parsed when parsing the container format // check for ID3v2 tags: the offsets of the ID3v2 tags have already been parsed when parsing the container format
m_id3v2Tags.clear(); m_id3v2Tags.clear();
for (const auto offset : m_actualId3v2TagOffsets) { for (const auto offset : m_actualId3v2TagOffsets) {
@ -472,9 +415,6 @@ void MediaFileInfo::parseTags(Diagnostics &diag, AbortableProgressFeedback &prog
m_id3v2Tags.emplace_back(id3v2Tag.release()); m_id3v2Tags.emplace_back(id3v2Tag.release());
} }
// compute effective size
m_effectiveSize = static_cast<std::uint64_t>(effectiveSize - m_containerOffset);
// check for tags in tracks (FLAC only) or via container object // check for tags in tracks (FLAC only) or via container object
try { try {
if (m_containerFormat == ContainerFormat::Flac) { if (m_containerFormat == ContainerFormat::Flac) {
@ -1702,9 +1642,9 @@ void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &pr
progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ..."); progress.updateStep(flacStream ? "Updating FLAC tags ..." : "Updating ID3v2 tags ...");
// prepare ID3v2 tags // prepare ID3v2 tags
auto makers = std::vector<Id3v2TagMaker>(); vector<Id3v2TagMaker> makers;
makers.reserve(m_id3v2Tags.size()); makers.reserve(m_id3v2Tags.size());
auto tagsSize = std::uint64_t(); std::uint64_t tagsSize = 0;
for (auto &tag : m_id3v2Tags) { for (auto &tag : m_id3v2Tags) {
try { try {
makers.emplace_back(tag->prepareMaking(diag)); makers.emplace_back(tag->prepareMaking(diag));
@ -1714,10 +1654,10 @@ void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &pr
} }
// determine stream offset and make track/format specific metadata // determine stream offset and make track/format specific metadata
auto streamOffset = std::uint32_t(); // where the actual stream starts std::uint32_t streamOffset; // where the actual stream starts
auto flacMetaData = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary); stringstream flacMetaData(ios_base::in | ios_base::out | ios_base::binary);
flacMetaData.exceptions(std::ios_base::badbit | std::ios_base::failbit); flacMetaData.exceptions(ios_base::badbit | ios_base::failbit);
auto startOfLastMetaDataBlock = std::streamoff(); std::streamoff startOfLastMetaDataBlock;
if (flacStream) { if (flacStream) {
// if it is a raw FLAC stream, make FLAC metadata // if it is a raw FLAC stream, make FLAC metadata
startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag); startOfLastMetaDataBlock = flacStream->makeHeader(flacMetaData, diag);
@ -1729,8 +1669,8 @@ void MediaFileInfo::makeMp3File(Diagnostics &diag, AbortableProgressFeedback &pr
} }
// check whether rewrite is required // check whether rewrite is required
auto rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset); bool rewriteRequired = isForcingRewrite() || !m_saveFilePath.empty() || (tagsSize > streamOffset);
auto padding = std::size_t(); size_t padding = 0;
if (!rewriteRequired) { if (!rewriteRequired) {
// rewriting is not forced and new tag is not too big for available space // rewriting is not forced and new tag is not too big for available space
// -> calculate new padding // -> calculate new padding

View File

@ -64,11 +64,6 @@ enum class MediaFileHandlingFlags : std::uint64_t {
ForceIndexPosition = (1 << 3), /**< enforces the index position when applying changes, see remarks of MediaFileInfo::setIndexPosition() */ ForceIndexPosition = (1 << 3), /**< enforces the index position when applying changes, see remarks of MediaFileInfo::setIndexPosition() */
NormalizeKnownTagFieldIds = (1 << 4), /**< normalizes known tag field IDs when parsing to match the tag specification's recommendations */ NormalizeKnownTagFieldIds = (1 << 4), /**< normalizes known tag field IDs when parsing to match the tag specification's recommendations */
PreserveRawTimingValues = (1 << 8), /**< preverves raw timing values (so far only used when making MP4 tracks) */ PreserveRawTimingValues = (1 << 8), /**< preverves raw timing values (so far only used when making MP4 tracks) */
PreserveMuxingApplication = (1 << 9), /**< preverves the muxing application (so far only used when making Matroska container) */
PreserveWritingApplication = (1 << 10), /**< preverves the writing application (so far only used when making Matroska container) */
ConvertTotalFields = (1 << 11), /**< ensures fields usually holding PositionInSet values such as KnownField::TrackPosition are actually
stored as such (and *not* as two separate fields for the position and total values); currently only relevant for Vorbis Comments
\sa VorbisCommentFlags::ConvertTotalFields */
}; };
} // namespace TagParser } // namespace TagParser
@ -78,8 +73,6 @@ CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(TagParser, TagParser::MediaFileHandlingFlags)
namespace TagParser { namespace TagParser {
struct MediaFileInfoPrivate;
class TAG_PARSER_EXPORT MediaFileInfo : public BasicFileInfo { class TAG_PARSER_EXPORT MediaFileInfo : public BasicFileInfo {
public: public:
// constructor, destructor // constructor, destructor
@ -110,10 +103,9 @@ public:
std::string_view mimeType() const; std::string_view mimeType() const;
std::uint64_t containerOffset() const; std::uint64_t containerOffset() const;
std::uint64_t paddingSize() const; std::uint64_t paddingSize() const;
std::uint64_t effectiveSize() const;
AbstractContainer *container() const; AbstractContainer *container() const;
ParsingStatus containerParsingStatus() const; ParsingStatus containerParsingStatus() const;
// ... the chapters // ... the capters
ParsingStatus chaptersParsingStatus() const; ParsingStatus chaptersParsingStatus() const;
std::vector<AbstractChapter *> chapters() const; std::vector<AbstractChapter *> chapters() const;
bool areChaptersSupported() const; bool areChaptersSupported() const;
@ -162,7 +154,6 @@ public:
VorbisComment *createVorbisComment(); VorbisComment *createVorbisComment();
bool removeVorbisComment(); bool removeVorbisComment();
void clearParsingResults(); void clearParsingResults();
void reportPaddingSizeChanged(std::uint64_t newPaddingSize);
// methods to get, set object behaviour // methods to get, set object behaviour
const std::string &backupDirectory() const; const std::string &backupDirectory() const;
@ -193,8 +184,6 @@ public:
void setIndexPosition(ElementPosition indexPosition); void setIndexPosition(ElementPosition indexPosition);
bool forceIndexPosition() const; bool forceIndexPosition() const;
void setForceIndexPosition(bool forceTagPosition); void setForceIndexPosition(bool forceTagPosition);
std::uint64_t maxFullParseSize() const;
void setMaxFullParseSize(std::uint64_t maxFullParseSize);
protected: protected:
void invalidated() override; void invalidated() override;
@ -210,7 +199,6 @@ private:
ContainerFormat m_containerFormat; ContainerFormat m_containerFormat;
std::streamoff m_containerOffset; std::streamoff m_containerOffset;
std::uint64_t m_paddingSize; std::uint64_t m_paddingSize;
std::uint64_t m_effectiveSize;
std::vector<std::streamoff> m_actualId3v2TagOffsets; std::vector<std::streamoff> m_actualId3v2TagOffsets;
std::unique_ptr<AbstractContainer> m_container; std::unique_ptr<AbstractContainer> m_container;
MediaFileStructureFlags m_fileStructureFlags; MediaFileStructureFlags m_fileStructureFlags;
@ -238,8 +226,6 @@ private:
ElementPosition m_tagPosition; ElementPosition m_tagPosition;
ElementPosition m_indexPosition; ElementPosition m_indexPosition;
MediaFileHandlingFlags m_fileHandlingFlags; MediaFileHandlingFlags m_fileHandlingFlags;
std::uint64_t m_maxFullParseSize;
std::unique_ptr<MediaFileInfoPrivate> m_p;
}; };
/*! /*!
@ -307,15 +293,6 @@ inline std::uint64_t MediaFileInfo::paddingSize() const
return m_paddingSize; return m_paddingSize;
} }
/*!
* \brief Returns the "effective size" of the file if know; otherwise returns 0.
* \remarks This is the size of the file minus tags at the beginning and the end.
*/
inline std::uint64_t MediaFileInfo::effectiveSize() const
{
return m_effectiveSize;
}
/*! /*!
* \brief Returns an indication whether tag information has been parsed yet. * \brief Returns an indication whether tag information has been parsed yet.
*/ */
@ -401,15 +378,6 @@ inline const std::vector<std::unique_ptr<Id3v2Tag>> &MediaFileInfo::id3v2Tags()
return m_id3v2Tags; return m_id3v2Tags;
} }
/*!
* \brief Sets the padding size.
* \remarks This is meant to be called from container implementations.
*/
inline void MediaFileInfo::reportPaddingSizeChanged(uint64_t newPaddingSize)
{
m_paddingSize = newPaddingSize;
}
/*! /*!
* \brief Returns the directory used to store backup files. * \brief Returns the directory used to store backup files.
* \remarks If empty, backup files will be stored in the same directory of the file being modified. * \remarks If empty, backup files will be stored in the same directory of the file being modified.
@ -486,9 +454,7 @@ inline const std::string &MediaFileInfo::writingApplication() const
/*! /*!
* \brief Sets the writing application as container-level meta-data. Put the name of your application here. * \brief Sets the writing application as container-level meta-data. Put the name of your application here.
* \remarks * \remarks Might not be used (depends on the format).
* - Currently only used when making Matroska files.
* - The assigned value is ignored when MediaFileHandlingFlags::PreserveWritingApplication is set.
*/ */
inline void MediaFileInfo::setWritingApplication(std::string_view writingApplication) inline void MediaFileInfo::setWritingApplication(std::string_view writingApplication)
{ {
@ -726,33 +692,6 @@ inline void MediaFileInfo::setForceIndexPosition(bool forceIndexPosition)
CppUtilities::modFlagEnum(m_fileHandlingFlags, MediaFileHandlingFlags::ForceIndexPosition, forceIndexPosition); CppUtilities::modFlagEnum(m_fileHandlingFlags, MediaFileHandlingFlags::ForceIndexPosition, forceIndexPosition);
} }
/*!
* \brief Returns the maximal file size for a "full parse" in byte.
* \remarks
* So far this is Matroska-specific: The "Tags" element (which holds the tag information) is commonly at the end
* of a Matroska file. Hence the parser needs to walk through the entire file to find the tag information if no
* "SeekHead" element is present which might causes long loading times. To avoid this a maximal file size for a
* "full parse" can be specified. The disadvantage is that the parser relies on the presence of a SeekHead element
* on larger files to retrieve tag information.
*
* The default value is 50 MiB.
*
* \sa setMaxFullParseSize()
*/
inline std::uint64_t MediaFileInfo::maxFullParseSize() const
{
return m_maxFullParseSize;
}
/*!
* \brief Sets the maximal file size for a "full parse" in byte.
* \sa maxFullParseSize()
*/
inline void MediaFileInfo::setMaxFullParseSize(std::uint64_t maxFullParseSize)
{
m_maxFullParseSize = maxFullParseSize;
}
} // namespace TagParser } // namespace TagParser
#endif // TAG_PARSER_MEDIAINFO_H #endif // TAG_PARSER_MEDIAINFO_H

View File

@ -1,3 +1,5 @@
#define CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
#include "./mp4container.h" #include "./mp4container.h"
#include "./mp4ids.h" #include "./mp4ids.h"
@ -267,7 +269,7 @@ void Mp4Container::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
// find relevant atoms in original file // find relevant atoms in original file
Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom /*, *userDataAtom*/; Mp4Atom *fileTypeAtom, *progressiveDownloadInfoAtom, *movieAtom, *firstMediaDataAtom, *firstMovieFragmentAtom /*, *userDataAtom*/;
Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten = nullptr; Mp4Atom *level0Atom, *level1Atom, *level2Atom, *lastAtomToBeWritten;
try { try {
// file type atom (mandatory) // file type atom (mandatory)
if ((fileTypeAtom = firstElement()->siblingByIdIncludingThis(Mp4AtomIds::FileType, diag))) { if ((fileTypeAtom = firstElement()->siblingByIdIncludingThis(Mp4AtomIds::FileType, diag))) {
@ -747,7 +749,7 @@ calculatePadding:
// increase total chunk count and size // increase total chunk count and size
totalChunkCount += track->chunkCount(); totalChunkCount += track->chunkCount();
totalMediaDataSize += std::accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), static_cast<std::uint64_t>(0u)); totalMediaDataSize += accumulate(chunkSizesTable.cbegin(), chunkSizesTable.cend(), 0ul);
} }
// write media data chunk-by-chunk // write media data chunk-by-chunk

View File

@ -257,7 +257,7 @@ inline Mp4TagField::IdentifierType Mp4TagField::fieldIdFromString(std::string_vi
const auto latin1 = CppUtilities::convertUtf8ToLatin1(idString.data(), idString.size()); const auto latin1 = CppUtilities::convertUtf8ToLatin1(idString.data(), idString.size());
switch (latin1.second) { switch (latin1.second) {
case 4: case 4:
return CppUtilities::BE::toInt<std::uint32_t>(latin1.first.get()); return CppUtilities::BE::toUInt32(latin1.first.get());
default: default:
throw CppUtilities::ConversionException("MP4 ID must be exactly 4 chars"); throw CppUtilities::ConversionException("MP4 ID must be exactly 4 chars");
} }

View File

@ -1,3 +1,5 @@
#define CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
#include "./mp4track.h" #include "./mp4track.h"
#include "./mp4atom.h" #include "./mp4atom.h"
#include "./mp4container.h" #include "./mp4container.h"

View File

@ -74,7 +74,7 @@ void MpegAudioFrame::parseHeader(BinaryReader &reader, Diagnostics &diag)
m_xingBytesfield = reader.readUInt32BE(); m_xingBytesfield = reader.readUInt32BE();
} }
if (isXingTocFieldPresent()) { if (isXingTocFieldPresent()) {
reader.stream()->seekg(0x64, ios_base::cur); reader.stream()->seekg(64, ios_base::cur);
} }
if (isXingQualityIndicatorFieldPresent()) { if (isXingQualityIndicatorFieldPresent()) {
m_xingQualityIndicator = reader.readUInt32BE(); m_xingQualityIndicator = reader.readUInt32BE();

View File

@ -174,7 +174,7 @@ constexpr XingHeaderFlags MpegAudioFrame::xingHeaderFlags() const
*/ */
constexpr bool MpegAudioFrame::isXingFramefieldPresent() const constexpr bool MpegAudioFrame::isXingFramefieldPresent() const
{ {
return isXingHeaderAvailable() && ((m_xingHeaderFlags & XingHeaderFlags::HasFramesField) == XingHeaderFlags::HasFramesField); return (isXingHeaderAvailable()) ? ((m_xingHeaderFlags & XingHeaderFlags::HasFramesField) == XingHeaderFlags::HasFramesField) : false;
} }
/*! /*!
@ -182,7 +182,7 @@ constexpr bool MpegAudioFrame::isXingFramefieldPresent() const
*/ */
constexpr bool MpegAudioFrame::isXingBytesfieldPresent() const constexpr bool MpegAudioFrame::isXingBytesfieldPresent() const
{ {
return isXingHeaderAvailable() && ((m_xingHeaderFlags & XingHeaderFlags::HasBytesField) == XingHeaderFlags::HasBytesField); return (isXingHeaderAvailable()) ? ((m_xingHeaderFlags & XingHeaderFlags::HasFramesField) == XingHeaderFlags::HasFramesField) : false;
} }
/*! /*!
@ -190,7 +190,7 @@ constexpr bool MpegAudioFrame::isXingBytesfieldPresent() const
*/ */
constexpr bool MpegAudioFrame::isXingTocFieldPresent() const constexpr bool MpegAudioFrame::isXingTocFieldPresent() const
{ {
return isXingHeaderAvailable() && ((m_xingHeaderFlags & XingHeaderFlags::HasTocField) == XingHeaderFlags::HasTocField); return (isXingHeaderAvailable()) ? ((m_xingHeaderFlags & XingHeaderFlags::HasTocField) == XingHeaderFlags::HasTocField) : false;
} }
/*! /*!
@ -198,7 +198,7 @@ constexpr bool MpegAudioFrame::isXingTocFieldPresent() const
*/ */
constexpr bool MpegAudioFrame::isXingQualityIndicatorFieldPresent() const constexpr bool MpegAudioFrame::isXingQualityIndicatorFieldPresent() const
{ {
return isXingHeaderAvailable() && ((m_xingHeaderFlags & XingHeaderFlags::HasQualityIndicator) == XingHeaderFlags::HasQualityIndicator); return (isXingHeaderAvailable()) ? ((m_xingHeaderFlags & XingHeaderFlags::HasQualityIndicator) == XingHeaderFlags::HasQualityIndicator) : false;
} }
/*! /*!

View File

@ -3,8 +3,6 @@
#include "../exceptions.h" #include "../exceptions.h"
#include "../mediaformat.h" #include "../mediaformat.h"
#include <c++utilities/conversion/stringbuilder.h>
#include <sstream> #include <sstream>
using namespace std; using namespace std;
@ -37,13 +35,20 @@ void MpegAudioFrameStream::internalParseHeader(Diagnostics &diag, AbortableProgr
if (!m_istream) { if (!m_istream) {
throw NoDataFoundException(); throw NoDataFoundException();
} }
// get size
m_istream->seekg(-128, ios_base::end);
if (m_reader.readUInt24BE() == 0x544147) {
m_size = static_cast<std::uint64_t>(m_istream->tellg()) - 3u - m_startOffset;
} else {
m_size = static_cast<std::uint64_t>(m_istream->tellg()) + 125u - m_startOffset;
}
m_istream->seekg(static_cast<streamoff>(m_startOffset), ios_base::beg);
// parse frames until the first valid, non-empty frame is reached // parse frames until the first valid, non-empty frame is reached
m_istream->seekg(static_cast<std::streamoff>(m_startOffset), ios_base::beg);
for (size_t invalidByteskipped = 0; m_frames.size() < 200 && invalidByteskipped <= 0x600u;) { for (size_t invalidByteskipped = 0; m_frames.size() < 200 && invalidByteskipped <= 0x600u;) {
MpegAudioFrame &frame = invalidByteskipped > 0 ? m_frames.back() : m_frames.emplace_back(); MpegAudioFrame &frame = invalidByteskipped > 0 ? m_frames.back() : m_frames.emplace_back();
try { try {
frame.parseHeader(m_reader, diag); frame.parseHeader(m_reader, diag);
} catch (const InvalidDataException &) { } catch (const InvalidDataException &e) {
if (++invalidByteskipped > 1) { if (++invalidByteskipped > 1) {
diag.pop_back(); diag.pop_back();
} }
@ -68,24 +73,18 @@ void MpegAudioFrameStream::internalParseHeader(Diagnostics &diag, AbortableProgr
const MpegAudioFrame &frame = m_frames.back(); const MpegAudioFrame &frame = m_frames.back();
addInfo(frame, *this); addInfo(frame, *this);
if (frame.isXingBytesfieldPresent()) { if (frame.isXingBytesfieldPresent()) {
const auto xingSize = frame.xingBytesfield(); std::uint32_t xingSize = frame.xingBytesfield();
if (!m_size) { if (m_size && xingSize != m_size) {
m_size = xingSize;
} else if (xingSize != m_size) {
diag.emplace_back(DiagLevel::Warning, diag.emplace_back(DiagLevel::Warning,
argsToString("Real size of MPEG audio frames (", m_size, " byte) is not in accordance with value provided by Xing header (", xingSize, "Real length of MPEG audio frames is not in accordance with value provided by Xing header. The Xing header value will be used.",
" byte). The real size will be used."),
context); context);
m_size = xingSize;
} }
} }
if (frame.isXingFramefieldPresent()) { m_bitrate = frame.isXingFramefieldPresent() ? ((static_cast<double>(m_size) * 8.0)
const auto duration = static_cast<double>(frame.xingFrameCount() * frame.sampleCount()) / static_cast<double>(frame.samplingFrequency()); / (static_cast<double>(frame.xingFrameCount() * frame.sampleCount()) / static_cast<double>(frame.samplingFrequency())) / 1024.0)
m_bitrate = static_cast<double>(m_size) / duration / 125.0; : frame.bitrate();
m_duration = TimeSpan::fromSeconds(duration);
} else {
m_bitrate = frame.bitrate();
m_duration = TimeSpan::fromSeconds(static_cast<double>(m_size) / (m_bytesPerSecond = static_cast<std::uint32_t>(m_bitrate * 125))); m_duration = TimeSpan::fromSeconds(static_cast<double>(m_size) / (m_bytesPerSecond = static_cast<std::uint32_t>(m_bitrate * 125)));
}
} }
} // namespace TagParser } // namespace TagParser

View File

@ -10,7 +10,6 @@
#include <c++utilities/conversion/stringbuilder.h> #include <c++utilities/conversion/stringbuilder.h>
#include <c++utilities/io/copy.h> #include <c++utilities/io/copy.h>
#include <limits>
#include <memory> #include <memory>
using namespace std; using namespace std;
@ -20,7 +19,7 @@ namespace TagParser {
/*! /*!
* \class TagParser::OggVorbisComment * \class TagParser::OggVorbisComment
* \brief Specialization of TagParser::VorbisComment for Vorbis comments inside an Ogg stream. * \brief Specialization of TagParser::VorbisComment for Vorbis comments inside an OGG stream.
*/ */
std::string_view OggVorbisComment::typeName() const std::string_view OggVorbisComment::typeName() const
@ -39,7 +38,7 @@ std::string_view OggVorbisComment::typeName() const
/*! /*!
* \class TagParser::OggContainer * \class TagParser::OggContainer
* \brief Implementation of TagParser::AbstractContainer for Ogg files. * \brief Implementation of TagParser::AbstractContainer for OGG files.
*/ */
/*! /*!
@ -121,9 +120,9 @@ OggVorbisComment *OggContainer::createTag(const TagTarget &target)
return nullptr; return nullptr;
} }
OggVorbisComment *OggContainer::tag(std::size_t index) OggVorbisComment *OggContainer::tag(size_t index)
{ {
auto i = std::size_t(); size_t i = 0;
for (const auto &tag : m_tags) { for (const auto &tag : m_tags) {
if (!tag->oggParams().removed) { if (!tag->oggParams().removed) {
if (index == i) { if (index == i) {
@ -137,7 +136,7 @@ OggVorbisComment *OggContainer::tag(std::size_t index)
size_t OggContainer::tagCount() const size_t OggContainer::tagCount() const
{ {
auto count = std::size_t(); size_t count = 0;
for (const auto &tag : m_tags) { for (const auto &tag : m_tags) {
if (!tag->oggParams().removed) { if (!tag->oggParams().removed) {
++count; ++count;
@ -150,7 +149,7 @@ size_t OggContainer::tagCount() const
* \brief Actually just flags the specified \a tag as removed and clears all assigned fields. * \brief Actually just flags the specified \a tag as removed and clears all assigned fields.
* *
* This specialization is necessary because removing the tag completely would also * This specialization is necessary because removing the tag completely would also
* remove the Ogg parameter which are needed when applying changes. * remove the OGG parameter which are needed when applying changes.
* *
* \remarks Seems like common players aren't able to play Vorbis when no comment is present. * \remarks Seems like common players aren't able to play Vorbis when no comment is present.
* So do NOT use this method to remove tags from Vorbis, just call Tag::removeAllFields() on \a tag. * So do NOT use this method to remove tags from Vorbis, just call Tag::removeAllFields() on \a tag.
@ -171,7 +170,7 @@ bool OggContainer::removeTag(Tag *tag)
* \brief Actually just flags all tags as removed and clears all assigned fields. * \brief Actually just flags all tags as removed and clears all assigned fields.
* *
* This specialization is necessary because completeley removing the tag would also * This specialization is necessary because completeley removing the tag would also
* remove the Ogg parameter which are needed when applying the changes. * remove the OGG parameter which are needed when applying the changes.
* *
* \remarks Seems like common players aren't able to play Vorbis when no comment is present. * \remarks Seems like common players aren't able to play Vorbis when no comment is present.
* So do NOT use this method to remove tags from Vorbis, just call removeAllFields() on all tags. * So do NOT use this method to remove tags from Vorbis, just call removeAllFields() on all tags.
@ -189,8 +188,8 @@ void OggContainer::internalParseHeader(Diagnostics &diag, AbortableProgressFeedb
{ {
CPP_UTILITIES_UNUSED(progress) CPP_UTILITIES_UNUSED(progress)
static const auto context = std::string("parsing Ogg bitstream header"); static const string context("parsing OGG bitstream header");
auto pagesSkipped = false, continueFromHere = false; bool pagesSkipped = false, continueFromHere = false;
// iterate through pages using OggIterator helper class // iterate through pages using OggIterator helper class
try { try {
@ -202,17 +201,18 @@ void OggContainer::internalParseHeader(Diagnostics &diag, AbortableProgressFeedb
if (m_validateChecksums && page.checksum() != OggPage::computeChecksum(stream(), page.startOffset())) { if (m_validateChecksums && page.checksum() != OggPage::computeChecksum(stream(), page.startOffset())) {
diag.emplace_back(DiagLevel::Warning, diag.emplace_back(DiagLevel::Warning,
argsToString( argsToString(
"The denoted checksum of the Ogg page at ", m_iterator.currentSegmentOffset(), " does not match the computed checksum."), "The denoted checksum of the OGG page at ", m_iterator.currentSegmentOffset(), " does not match the computed checksum."),
context); context);
} }
OggStream *stream; OggStream *stream;
auto lastNewStreamOffset = std::uint64_t(); std::uint64_t lastNewStreamOffset = 0;
if (const auto streamIndex = m_streamsBySerialNo.find(page.streamSerialNumber()); streamIndex != m_streamsBySerialNo.end()) { if (const auto streamIndex = m_streamsBySerialNo.find(page.streamSerialNumber()); streamIndex != m_streamsBySerialNo.end()) {
stream = m_tracks[streamIndex->second].get(); stream = m_tracks[streamIndex->second].get();
} else { } else {
// new stream serial number recognized -> add new stream // new stream serial number recognized -> add new stream
m_streamsBySerialNo[page.streamSerialNumber()] = m_tracks.size(); m_streamsBySerialNo[page.streamSerialNumber()] = m_tracks.size();
stream = m_tracks.emplace_back(make_unique<OggStream>(*this, m_iterator.currentPageIndex())).get(); m_tracks.emplace_back(make_unique<OggStream>(*this, m_iterator.currentPageIndex()));
stream = m_tracks.back().get();
lastNewStreamOffset = page.startOffset(); lastNewStreamOffset = page.startOffset();
} }
if (!pagesSkipped) { if (!pagesSkipped) {
@ -249,14 +249,14 @@ void OggContainer::internalParseHeader(Diagnostics &diag, AbortableProgressFeedb
} else { } else {
// abort if skipping pages didn't work // abort if skipping pages didn't work
diag.emplace_back(DiagLevel::Critical, diag.emplace_back(DiagLevel::Critical,
"Unable to re-sync after skipping Ogg pages in the middle of the file. Try forcing a full parse.", context); "Unable to re-sync after skipping OGG pages in the middle of the file. Try forcing a full parse.", context);
return; return;
} }
} }
} }
} catch (const TruncatedDataException &) { } catch (const TruncatedDataException &) {
// thrown when page exceeds max size // thrown when page exceeds max size
diag.emplace_back(DiagLevel::Critical, "The Ogg file is truncated.", context); diag.emplace_back(DiagLevel::Critical, "The OGG file is truncated.", context);
} catch (const InvalidDataException &) { } catch (const InvalidDataException &) {
// thrown when first 4 byte do not match capture pattern // thrown when first 4 byte do not match capture pattern
const auto expectedOffset = m_iterator.currentSegmentOffset(); const auto expectedOffset = m_iterator.currentSegmentOffset();
@ -280,62 +280,29 @@ void OggContainer::internalParseTags(Diagnostics &diag, AbortableProgressFeedbac
{ {
// tracks needs to be parsed before because tags are stored at stream level // tracks needs to be parsed before because tags are stored at stream level
parseTracks(diag, progress); parseTracks(diag, progress);
auto flags = VorbisCommentFlags::None;
if (fileInfo().fileHandlingFlags() & MediaFileHandlingFlags::ConvertTotalFields) {
flags += VorbisCommentFlags::ConvertTotalFields;
}
for (auto &comment : m_tags) { for (auto &comment : m_tags) {
OggParameter &params = comment->oggParams(); OggParameter &params = comment->oggParams();
m_iterator.setPageIndex(params.firstPageIndex); m_iterator.setPageIndex(params.firstPageIndex);
m_iterator.setSegmentIndex(params.firstSegmentIndex); m_iterator.setSegmentIndex(params.firstSegmentIndex);
m_iterator.setFilter(m_iterator.currentPage().streamSerialNumber()); m_iterator.setFilter(m_iterator.currentPage().streamSerialNumber());
const auto startOffset = m_iterator.startOffset();
const auto context = argsToString("parsing tag in Ogg page at ", startOffset);
auto padding = std::uint64_t();
switch (params.streamFormat) { switch (params.streamFormat) {
case GeneralMediaFormat::Vorbis: case GeneralMediaFormat::Vorbis:
comment->parse(m_iterator, flags, padding, diag); comment->parse(m_iterator, VorbisCommentFlags::None, diag);
break; break;
case GeneralMediaFormat::Opus: case GeneralMediaFormat::Opus:
// skip header (has already been detected by OggStream) // skip header (has already been detected by OggStream)
m_iterator.ignore(8); m_iterator.ignore(8);
comment->parse(m_iterator, flags | VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, padding, diag); comment->parse(m_iterator, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, diag);
break; break;
case GeneralMediaFormat::Flac: case GeneralMediaFormat::Flac:
m_iterator.ignore(4); m_iterator.ignore(4);
comment->parse(m_iterator, flags | VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, padding, diag); comment->parse(m_iterator, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, diag);
break; break;
default: default:
diag.emplace_back(DiagLevel::Critical, "Stream format not supported.", context); diag.emplace_back(DiagLevel::Critical, "Stream format not supported.", "parsing tags from OGG streams");
} }
params.lastPageIndex = m_iterator.currentPageIndex(); params.lastPageIndex = m_iterator.currentPageIndex();
params.lastSegmentIndex = m_iterator.currentSegmentIndex(); params.lastSegmentIndex = m_iterator.currentSegmentIndex();
fileInfo().reportPaddingSizeChanged(fileInfo().paddingSize() + padding);
// do a few sanity checks on the continued-flag and absolute granule position as some Ogg demuxers are picky about them
static constexpr auto noPacketsFinishOnPage = std::numeric_limits<std::uint64_t>::max();
if (params.firstPageIndex != params.lastPageIndex) {
const auto pageCount = params.lastPageIndex - params.firstPageIndex;
for (auto i = params.firstPageIndex; i < params.lastPageIndex; ++i) {
if (const auto &page = m_iterator.pages()[i]; page.absoluteGranulePosition() != noPacketsFinishOnPage) {
diag.emplace_back(DiagLevel::Warning,
argsToString("Tag spans over ", pageCount, " pages but absolute granule position of unfinished page at ", page.startOffset(),
" is not set to \"-1\" (it is ", page.absoluteGranulePosition(), ")."),
context);
}
}
for (auto i = params.firstPageIndex + 1; i <= params.lastPageIndex; ++i) {
if (const auto &page = m_iterator.pages()[i]; !page.isContinued()) {
diag.emplace_back(DiagLevel::Warning,
argsToString("The tag is continued in Ogg page at ", page.startOffset(), " but this page is not marked as continued packet."),
context);
}
}
}
if (const auto &page = m_iterator.pages()[params.lastPageIndex]; page.absoluteGranulePosition() == noPacketsFinishOnPage) {
diag.emplace_back(
DiagLevel::Warning, argsToString("Absolute granule position of final page at ", page.startOffset(), " is set to \"-1\"."), context);
}
} }
} }
@ -359,7 +326,7 @@ void OggContainer::announceComment(std::size_t pageIndex, std::size_t segmentInd
void OggContainer::internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress) void OggContainer::internalParseTracks(Diagnostics &diag, AbortableProgressFeedback &progress)
{ {
static const string context("parsing Ogg stream"); static const string context("parsing OGG stream");
for (auto &stream : m_tracks) { for (auto &stream : m_tracks) {
if (progress.isAborted()) { if (progress.isAborted()) {
throw OperationAbortedException(); throw OperationAbortedException();
@ -416,19 +383,16 @@ void OggContainer::makeVorbisCommentSegment(stringstream &buffer, CopyHelper<653
} }
default:; default:;
} }
for (auto padding = fileInfo().preferredPadding(); padding; --padding) {
buffer.put(0);
}
newSegmentSizes.push_back(static_cast<std::uint32_t>(buffer.tellp() - offset)); newSegmentSizes.push_back(static_cast<std::uint32_t>(buffer.tellp() - offset));
} }
void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress) void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback &progress)
{ {
const auto context = std::string("making Ogg file"); const string context("making OGG file");
progress.nextStepOrStop("Prepare for rewriting Ogg file ..."); progress.nextStepOrStop("Prepare for rewriting OGG file ...");
parseTags(diag, progress); // tags need to be parsed before the file can be rewritten parseTags(diag, progress); // tags need to be parsed before the file can be rewritten
auto originalPath = fileInfo().path(), backupPath = std::string(); string originalPath = fileInfo().path(), backupPath;
auto backupStream = NativeFileStream(); NativeFileStream backupStream;
if (fileInfo().saveFilePath().empty()) { if (fileInfo().saveFilePath().empty()) {
// move current file to temp dir and reopen it as backupStream, recreate original file // move current file to temp dir and reopen it as backupStream, recreate original file
@ -457,7 +421,7 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
const auto totalFileSize = fileInfo().size(); const auto totalFileSize = fileInfo().size();
try { try {
progress.nextStepOrStop("Writing Ogg pages ..."); progress.nextStepOrStop("Writing OGG pages ...");
// prepare iterating comments // prepare iterating comments
OggVorbisComment *currentComment; OggVorbisComment *currentComment;
@ -471,13 +435,11 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
} }
// define misc variables // define misc variables
CopyHelper<65307> copyHelper;
vector<std::uint64_t> updatedPageOffsets;
const OggPage *lastPage = nullptr; const OggPage *lastPage = nullptr;
static constexpr auto oggPageHeaderSize = 27u; std::uint64_t nextPageOffset;
auto lastPageNewOffset = std::uint64_t(); unordered_map<std::uint32_t, std::uint32_t> pageSequenceNumberBySerialNo;
auto copyHelper = CopyHelper<65307>();
auto updatedPageOffsets = std::vector<std::uint64_t>();
auto nextPageOffset = std::uint64_t();
auto pageSequenceNumberBySerialNo = std::unordered_map<std::uint32_t, std::uint32_t>();
// iterate through all pages of the original file // iterate through all pages of the original file
auto updateTick = 0u; auto updateTick = 0u;
@ -498,7 +460,7 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
const auto actuallyNextPageOffset = m_iterator.currentPageOffset(); const auto actuallyNextPageOffset = m_iterator.currentPageOffset();
if (actuallyNextPageOffset != nextPageOffset) { if (actuallyNextPageOffset != nextPageOffset) {
diag.emplace_back(DiagLevel::Critical, diag.emplace_back(DiagLevel::Critical,
argsToString("Expected Ogg page at offset ", nextPageOffset, " but found the next Ogg page only at offset ", argsToString("Expected OGG page at offset ", nextPageOffset, " but found the next OGG page only at offset ",
actuallyNextPageOffset, ". Skipped ", (actuallyNextPageOffset - nextPageOffset), " invalid bytes."), actuallyNextPageOffset, ". Skipped ", (actuallyNextPageOffset - nextPageOffset), " invalid bytes."),
context); context);
nextPageOffset = actuallyNextPageOffset; nextPageOffset = actuallyNextPageOffset;
@ -508,15 +470,14 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
} else { } else {
diag.emplace_back(DiagLevel::Critical, diag.emplace_back(DiagLevel::Critical,
argsToString( argsToString(
"Expected Ogg page at offset ", nextPageOffset, " but could not find any further pages. Skipped the rest of the file."), "Expected OGG page at offset ", nextPageOffset, " but could not find any further pages. Skipped the rest of the file."),
context); context);
break; break;
} }
} }
const auto pageSize = currentPage.totalSize(); const auto pageSize = currentPage.totalSize();
auto &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.streamSerialNumber()]; std::uint32_t &pageSequenceNumber = pageSequenceNumberBySerialNo[currentPage.streamSerialNumber()];
lastPage = &currentPage; lastPage = &currentPage;
lastPageNewOffset = static_cast<std::uint64_t>(stream().tellp());
nextPageOffset = currentPage.startOffset() + pageSize; nextPageOffset = currentPage.startOffset() + pageSize;
// check whether the Vorbis Comment is present in this Ogg page // check whether the Vorbis Comment is present in this Ogg page
@ -524,11 +485,11 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
&& m_iterator.currentPageIndex() <= currentParams->lastPageIndex && !currentPage.segmentSizes().empty()) { && m_iterator.currentPageIndex() <= currentParams->lastPageIndex && !currentPage.segmentSizes().empty()) {
// page needs to be rewritten (not just copied) // page needs to be rewritten (not just copied)
// -> write segments to a buffer first // -> write segments to a buffer first
auto buffer = std::stringstream(std::ios_base::in | std::ios_base::out | std::ios_base::binary); stringstream buffer(ios_base::in | ios_base::out | ios_base::binary);
auto newSegmentSizes = std::vector<std::uint32_t>(); vector<std::uint32_t> newSegmentSizes;
newSegmentSizes.reserve(currentPage.segmentSizes().size()); newSegmentSizes.reserve(currentPage.segmentSizes().size());
auto segmentOffset = m_iterator.currentSegmentOffset(); std::uint64_t segmentOffset = m_iterator.currentSegmentOffset();
auto segmentIndex = std::vector<std::uint32_t>::size_type(); vector<std::uint32_t>::size_type segmentIndex = 0;
for (const auto segmentSize : currentPage.segmentSizes()) { for (const auto segmentSize : currentPage.segmentSizes()) {
if (!segmentSize) { if (!segmentSize) {
++segmentIndex; ++segmentIndex;
@ -557,7 +518,7 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
} }
} else { } else {
// copy other segments unchanged // copy other segments unchanged
backupStream.seekg(static_cast<std::streamoff>(segmentOffset)); backupStream.seekg(static_cast<streamoff>(segmentOffset));
copyHelper.copy(backupStream, buffer, segmentSize); copyHelper.copy(backupStream, buffer, segmentSize);
newSegmentSizes.push_back(segmentSize); newSegmentSizes.push_back(segmentSize);
@ -587,30 +548,22 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
auto continuePreviousSegment = false, needsZeroLacingValue = false; auto continuePreviousSegment = false, needsZeroLacingValue = false;
// write pages until all data in the buffer is written // write pages until all data in the buffer is written
while (newSegmentSizesIterator != newSegmentSizesEnd) { while (newSegmentSizesIterator != newSegmentSizesEnd) {
// memorize offset to update checksum later // write page header
updatedPageOffsets.push_back(static_cast<std::uint64_t>(stream().tellp()));
// copy page header from original file (except for the segment table)
backupStream.seekg(static_cast<streamoff>(currentPage.startOffset())); backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
copyHelper.copy(backupStream, stream(), oggPageHeaderSize); updatedPageOffsets.push_back(static_cast<std::uint64_t>(stream().tellp())); // memorize offset to update checksum later
// use flags of original page as base and adjust "continued packet"-flag as needed copyHelper.copy(backupStream, stream(), 27); // just copy header from original file
auto flags = (currentPage.headerTypeFlag() & 0xFE) | (continuePreviousSegment ? 0x01 : 0x00); // set continue flag
continuePreviousSegment = true;
// ensure "first page of logical bitstream"-flag is cleared for additional pages we need to insert
// ensure "last page of logical bitstream"-flag is cleared for the first page
flags = flags & (newSegmentSizesIterator != newSegmentSizes.cbegin() ? 0xFD : 0xF);
// override flags copied from original file
stream().seekp(-22, ios_base::cur); stream().seekp(-22, ios_base::cur);
stream().put(static_cast<char>(flags)); stream().put(static_cast<char>(currentPage.headerTypeFlag() & (continuePreviousSegment ? 0xFF : 0xFE)));
// update absolute granule position later (8 byte) and keep stream serial number (4 byte) continuePreviousSegment = true;
stream().seekp(12, ios_base::cur);
// adjust page sequence number // adjust page sequence number
stream().seekp(12, ios_base::cur);
writer().writeUInt32LE(pageSequenceNumber); writer().writeUInt32LE(pageSequenceNumber);
// skip checksum (4 bytes) and number of page segments (1 byte); those are update later
stream().seekp(5, ios_base::cur); stream().seekp(5, ios_base::cur);
std::int16_t segmentSizesWritten = 0; // in the current page header only
// write segment sizes as long as there are segment sizes to be written and // write segment sizes as long as there are segment sizes to be written and
// the max number of segment sizes (255) is not exceeded // the max number of segment sizes (255) is not exceeded
auto segmentSizesWritten = std::int16_t(); // in the current page header only std::uint32_t currentSize = 0;
auto currentSize = std::uint32_t();
while ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) { while ((bytesLeft || needsZeroLacingValue) && segmentSizesWritten < 0xFF) {
while (bytesLeft > 0xFF && segmentSizesWritten < 0xFF) { while (bytesLeft > 0xFF && segmentSizesWritten < 0xFF) {
stream().put(static_cast<char>(0xFF)); stream().put(static_cast<char>(0xFF));
@ -636,31 +589,15 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
} }
} }
// remove continue flag if there are no bytes left in the current segment // there are no bytes left in the current segment; remove continue flag
if (!bytesLeft && !needsZeroLacingValue) { if (!bytesLeft && !needsZeroLacingValue) {
continuePreviousSegment = false; continuePreviousSegment = false;
} }
// set the absolute granule postion
if (newSegmentSizesIterator != newSegmentSizesEnd) {
// set absolute granule position to special value "-1" if there are still bytes to be written in the current packet
stream().seekp(-21 - segmentSizesWritten, ios_base::cur);
writer().writeInt64LE(-1);
stream().seekp(12, ios_base::cur);
} else if (currentParams->lastPageIndex != currentParams->firstPageIndex) {
// ensure the written absolute granule position matches the one from the last page of the existing file
backupStream.seekg(static_cast<streamoff>(m_iterator.pages()[currentParams->lastPageIndex].startOffset() + 6));
stream().seekp(-21 - segmentSizesWritten, ios_base::cur);
copyHelper.copy(backupStream, stream(), 8);
stream().seekp(12, ios_base::cur);
} else {
// leave the absolute granule position unchanged
stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
}
// page is full or all segment data has been covered // page is full or all segment data has been covered
// -> write segment table size (segmentSizesWritten) and segment data // -> write segment table size (segmentSizesWritten) and segment data
// -> seek back and write updated page segment number // -> seek back and write updated page segment number
stream().seekp(-1 - segmentSizesWritten, ios_base::cur);
stream().put(static_cast<char>(segmentSizesWritten)); stream().put(static_cast<char>(segmentSizesWritten));
stream().seekp(segmentSizesWritten, ios_base::cur); stream().seekp(segmentSizesWritten, ios_base::cur);
// -> write actual page data // -> write actual page data
@ -675,11 +612,11 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
// just update page sequence number // just update page sequence number
backupStream.seekg(static_cast<streamoff>(currentPage.startOffset())); backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
updatedPageOffsets.push_back(static_cast<std::uint64_t>(stream().tellp())); // memorize offset to update checksum later updatedPageOffsets.push_back(static_cast<std::uint64_t>(stream().tellp())); // memorize offset to update checksum later
copyHelper.copy(backupStream, stream(), oggPageHeaderSize); copyHelper.copy(backupStream, stream(), 27);
stream().seekp(-9, ios_base::cur); stream().seekp(-9, ios_base::cur);
writer().writeUInt32LE(pageSequenceNumber); writer().writeUInt32LE(pageSequenceNumber);
stream().seekp(5, ios_base::cur); stream().seekp(5, ios_base::cur);
copyHelper.copy(backupStream, stream(), pageSize - oggPageHeaderSize); copyHelper.copy(backupStream, stream(), pageSize - 27);
} else { } else {
// copy page unchanged // copy page unchanged
backupStream.seekg(static_cast<streamoff>(currentPage.startOffset())); backupStream.seekg(static_cast<streamoff>(currentPage.startOffset()));
@ -700,21 +637,9 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
} }
// close backups stream; reopen new file as readable stream // close backups stream; reopen new file as readable stream
auto &stream = fileInfo().stream();
backupStream.close(); backupStream.close();
fileInfo().close(); fileInfo().close();
stream.open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::out | ios_base::binary); fileInfo().stream().open(BasicFileInfo::pathForOpen(fileInfo().path()).data(), ios_base::in | ios_base::out | ios_base::binary);
// ensure the "last page of logical bitstream"-flag is set on the last Ogg page (in case the last page was written/modified by us)
if (lastPage && lastPageNewOffset) {
const auto offset = static_cast<std::streamoff>(lastPageNewOffset + 5ul);
stream.seekg(offset);
if (const auto flag = stream.get(); !(flag & 0x04)) {
updatedPageOffsets.emplace_back(lastPageNewOffset);
stream.seekp(offset);
stream.put(static_cast<char>(flag | 0x04));
}
}
// update checksums of modified pages // update checksums of modified pages
progress.nextStepOrStop("Updating checksums ..."); progress.nextStepOrStop("Updating checksums ...");
@ -724,15 +649,15 @@ void OggContainer::internalMakeFile(Diagnostics &diag, AbortableProgressFeedback
progress.updateStepPercentage(static_cast<std::uint8_t>(offset * 100ul / fileInfo().size())); progress.updateStepPercentage(static_cast<std::uint8_t>(offset * 100ul / fileInfo().size()));
progress.stopIfAborted(); progress.stopIfAborted();
} }
OggPage::updateChecksum(stream, offset); OggPage::updateChecksum(fileInfo().stream(), offset);
} }
// prevent deferring final write operations (to catch and handle possible errors here) // prevent deferring final write operations (to catch and handle possible errors here)
stream.flush(); fileInfo().stream().flush();
progress.updateStepPercentage(100); progress.updateStepPercentage(100);
// clear iterator // clear iterator
m_iterator.clear(stream, startOffset(), fileInfo().size()); m_iterator.clear(fileInfo().stream(), startOffset(), fileInfo().size());
} catch (...) { } catch (...) {
m_iterator.setStream(fileInfo().stream()); m_iterator.setStream(fileInfo().stream());

View File

@ -24,7 +24,7 @@ class MediaFileInfo;
class OggContainer; class OggContainer;
/*! /*!
* \brief The OggParameter struct holds the Ogg parameter for a VorbisComment. * \brief The OggParameter struct holds the OGG parameter for a VorbisComment.
*/ */
struct TAG_PARSER_EXPORT OggParameter { struct TAG_PARSER_EXPORT OggParameter {
constexpr OggParameter(); constexpr OggParameter();
@ -73,7 +73,7 @@ public:
OggVorbisComment(); OggVorbisComment();
static constexpr TagType tagType = TagType::OggVorbisComment; static constexpr TagType tagType = TagType::OggVorbisComment;
static constexpr std::string_view tagName = "Ogg Vorbis comment"; static constexpr std::string_view tagName = "OGG Vorbis comment";
TagType type() const override; TagType type() const override;
std::string_view typeName() const override; std::string_view typeName() const override;
bool supportsTarget() const override; bool supportsTarget() const override;
@ -86,7 +86,7 @@ private:
}; };
/*! /*!
* \brief Constructs a new Ogg Vorbis comment. * \brief Constructs a new OGG Vorbis comment.
*/ */
inline OggVorbisComment::OggVorbisComment() inline OggVorbisComment::OggVorbisComment()
{ {
@ -110,7 +110,7 @@ inline bool OggVorbisComment::supportsTarget() const
} }
/*! /*!
* \brief Returns the Ogg parameter for the comment. * \brief Returns the OGG parameter for the comment.
* *
* Consists of first page index, first segment index, last page index, last segment index and tag index (in this order). * Consists of first page index, first segment index, last page index, last segment index and tag index (in this order).
* These values are used and managed by the OggContainer class and do not affect the behavior of the VorbisComment instance. * These values are used and managed by the OggContainer class and do not affect the behavior of the VorbisComment instance.
@ -121,7 +121,7 @@ inline OggParameter &OggVorbisComment::oggParams()
} }
/*! /*!
* \brief Returns the Ogg parameter for the comment. * \brief Returns the OGG parameter for the comment.
* *
* Consists of first page index, first segment index, last page index, last segment index and tag index (in this order). * Consists of first page index, first segment index, last page index, last segment index and tag index (in this order).
* These values are used and managed by the OggContainer class and do not affect the behavior of the VorbisComment instance. * These values are used and managed by the OggContainer class and do not affect the behavior of the VorbisComment instance.
@ -169,7 +169,7 @@ private:
/*! /*!
* \brief Returns whether checksum validation is enabled. * \brief Returns whether checksum validation is enabled.
* *
* If checksum validation is enabled, the parser will validate the Ogg pages by * If checksum validation is enabled, the parser will validate the OGG pages by
* checking the CRC32 checksum. * checking the CRC32 checksum.
* *
* \sa setChecksumValidationEnabled() * \sa setChecksumValidationEnabled()

View File

@ -14,7 +14,7 @@ namespace TagParser {
/*! /*!
* \class TagParser::OggIterator * \class TagParser::OggIterator
* \brief The OggIterator class helps iterating through all segments of an Ogg bitstream. * \brief The OggIterator class helps iterating through all segments of an OGG bitstream.
* *
* If an OggIterator has just been constructed it is invalid. To fetch the first page from * If an OggIterator has just been constructed it is invalid. To fetch the first page from
* the stream call the reset() method. The iterator will now point to the first segment of the * the stream call the reset() method. The iterator will now point to the first segment of the
@ -22,7 +22,7 @@ namespace TagParser {
* *
* To go on call the appropriate methods. Parsing exceptions and IO exceptions might occur during iteration. * To go on call the appropriate methods. Parsing exceptions and IO exceptions might occur during iteration.
* *
* The internal buffer of Ogg pages might be accessed using the pages() method. * The internal buffer of OGG pages might be accessed using the pages() method.
*/ */
/*! /*!
@ -75,7 +75,7 @@ void OggIterator::nextPage()
} }
/*! /*!
* \brief Increases the current position by one segment. Enters the next page if the current segment is the last one in the current page. * \brief Increases the current position by one segment.
* \remarks The iterator must be valid. The iterator might be invalidated. * \remarks The iterator must be valid. The iterator might be invalidated.
*/ */
void OggIterator::nextSegment() void OggIterator::nextSegment()
@ -121,7 +121,7 @@ void OggIterator::previousSegment()
} }
/*! /*!
* \brief Reads \a count bytes from the Ogg stream and writes it to the specified \a buffer. * \brief Reads \a count bytes from the OGG stream and writes it to the specified \a buffer.
* \remarks * \remarks
* - Might increase the current page index and/or the current segment index. * - Might increase the current page index and/or the current segment index.
* - Page headers are skipped (this is the whole purpose of this method). * - Page headers are skipped (this is the whole purpose of this method).
@ -154,12 +154,12 @@ void OggIterator::read(char *buffer, std::size_t count)
} }
/*! /*!
* \brief Reads all bytes from the Ogg stream and writes it to the specified \a buffer. * \brief Reads all bytes from the OGG stream and writes it to the specified \a buffer.
* \remarks * \remarks
* - Might increase the current page index and/or the current segment index. * - Might increase the current page index and/or the current segment index.
* - Page headers are skipped (this is the whole purpose of this method). * - Page headers are skipped (this is the whole purpose of this method).
* - Does not write more than \a max bytes to the buffer. * - Does not write more than \a max bytes to the buffer.
* \returns Returns the number of bytes read from the Ogg stream. This might be less than \a max in * \returns Returns the number of bytes read from the OGG stream. This might be less than \a max in
* case not that many bytes were available. * case not that many bytes were available.
* \sa read() * \sa read()
* \sa currentCharacterOffset() * \sa currentCharacterOffset()
@ -187,7 +187,7 @@ std::size_t OggIterator::readAll(char *buffer, std::size_t max)
} }
/*! /*!
* \brief Advances the position of the next character to be read from the Ogg stream by \a count bytes. * \brief Advances the position of the next character to be read from the OGG stream by \a count bytes.
* \remarks * \remarks
* - Might increase the current page index and/or the current segment index. * - Might increase the current page index and/or the current segment index.
* - Page headers are skipped (this is the whole purpose of this method). * - Page headers are skipped (this is the whole purpose of this method).
@ -223,7 +223,7 @@ void OggIterator::ignore(std::size_t count)
* the last known page. Hence \a offset must be greater than OggPage::startOffset() + OggPage::totalSize() of the * the last known page. Hence \a offset must be greater than OggPage::startOffset() + OggPage::totalSize() of the
* last known page. This is checked by the method. * last known page. This is checked by the method.
* *
* If the Ogg capture pattern is not present at \a offset, up to 65307 bytes (max. size of an Ogg page) are * If the OGG capture pattern is not present at \a offset, up to 65307 bytes (max. size of an OGG page) are
* skipped. So in a valid stream, this method will always succeed if \a offset is less than the stream size minus * skipped. So in a valid stream, this method will always succeed if \a offset is less than the stream size minus
* 65307. * 65307.
* *
@ -256,7 +256,7 @@ bool OggIterator::resyncAt(std::uint64_t offset)
if (lettersFound == 3) { if (lettersFound == 3) {
// capture pattern found // capture pattern found
const auto currentOffset = stream().tellg(); const auto currentOffset = stream().tellg();
// -> try to parse an Ogg page at this position // -> try to parse an OGG page at this position
try { try {
m_pages.emplace_back(stream(), static_cast<std::uint64_t>(stream().tellg()) - 4, m_pages.emplace_back(stream(), static_cast<std::uint64_t>(stream().tellg()) - 4,
bytesAvailable > numeric_limits<std::int32_t>::max() ? numeric_limits<std::int32_t>::max() bytesAvailable > numeric_limits<std::int32_t>::max() ? numeric_limits<std::int32_t>::max()

View File

@ -120,7 +120,7 @@ inline std::uint64_t OggIterator::streamSize() const
} }
/*! /*!
* \brief Returns a vector of containing the Ogg pages that have been fetched yet. * \brief Returns a vector of containing the OGG pages that have been fetched yet.
*/ */
inline const std::vector<OggPage> &OggIterator::pages() const inline const std::vector<OggPage> &OggIterator::pages() const
{ {
@ -128,7 +128,7 @@ inline const std::vector<OggPage> &OggIterator::pages() const
} }
/*! /*!
* \brief Returns a vector of containing the Ogg pages that have been fetched yet. * \brief Returns a vector of containing the OGG pages that have been fetched yet.
*/ */
inline std::vector<OggPage> &OggIterator::pages() inline std::vector<OggPage> &OggIterator::pages()
{ {
@ -136,7 +136,7 @@ inline std::vector<OggPage> &OggIterator::pages()
} }
/*! /*!
* \brief Returns the current Ogg page. * \brief Returns the current OGG page.
* \remarks Calling this method when the iterator is invalid causes undefined behaviour. * \remarks Calling this method when the iterator is invalid causes undefined behaviour.
*/ */
inline const OggPage &OggIterator::currentPage() const inline const OggPage &OggIterator::currentPage() const
@ -145,7 +145,7 @@ inline const OggPage &OggIterator::currentPage() const
} }
/*! /*!
* \brief Returns the start offset of the current Ogg page. * \brief Returns the start offset of the current OGG page.
* \remarks Calling this method when the iterator is invalid causes undefined behaviour. * \remarks Calling this method when the iterator is invalid causes undefined behaviour.
*/ */
inline std::uint64_t OggIterator::currentPageOffset() const inline std::uint64_t OggIterator::currentPageOffset() const

View File

@ -14,7 +14,7 @@ namespace TagParser {
/*! /*!
* \class TagParser::OggPage * \class TagParser::OggPage
* \brief The OggPage class is used to parse Ogg pages. * \brief The OggPage class is used to parse OGG pages.
* \sa http://www.xiph.org/ogg/doc/framing.html * \sa http://www.xiph.org/ogg/doc/framing.html
* \todo Add field for additional flags in v11. * \todo Add field for additional flags in v11.
*/ */
@ -62,7 +62,7 @@ void OggPage::parseHeader(istream &stream, std::uint64_t startOffset, std::int32
if (++i < m_segmentCount && entry < 0xFF) { if (++i < m_segmentCount && entry < 0xFF) {
m_segmentSizes.emplace_back(0); m_segmentSizes.emplace_back(0);
} else if (i == m_segmentCount && entry == 0xFF) { } else if (i == m_segmentCount && entry == 0xFF) {
m_lastSegmentUnconcluded = true; m_headerTypeFlag |= 0x80; // FIXME v11: don't abuse header type flags
} }
} }
// check whether the maximum size is exceeded // check whether the maximum size is exceeded

View File

@ -48,12 +48,11 @@ private:
std::uint32_t m_sequenceNumber; std::uint32_t m_sequenceNumber;
std::uint32_t m_checksum; std::uint32_t m_checksum;
std::uint8_t m_segmentCount; std::uint8_t m_segmentCount;
bool m_lastSegmentUnconcluded;
std::vector<std::uint32_t> m_segmentSizes; std::vector<std::uint32_t> m_segmentSizes;
}; };
/*! /*!
* \brief Constructs a new Ogg page. * \brief Constructs a new OGG page.
*/ */
inline OggPage::OggPage() inline OggPage::OggPage()
: m_startOffset(0) : m_startOffset(0)
@ -64,7 +63,6 @@ inline OggPage::OggPage()
, m_sequenceNumber(0) , m_sequenceNumber(0)
, m_checksum(0) , m_checksum(0)
, m_segmentCount(0) , m_segmentCount(0)
, m_lastSegmentUnconcluded(false)
{ {
} }
@ -108,7 +106,7 @@ inline std::uint8_t OggPage::headerTypeFlag() const
} }
/*! /*!
* \brief Returns whether this page is a continued packet (true) or a fresh packet (false). * \brief Returns whether this page is a continued packed (true) or a fresh packed (false).
*/ */
inline bool OggPage::isContinued() const inline bool OggPage::isContinued() const
{ {
@ -136,7 +134,7 @@ inline bool OggPage::isLastPage() const
*/ */
inline bool OggPage::isLastSegmentUnconcluded() const inline bool OggPage::isLastSegmentUnconcluded() const
{ {
return m_lastSegmentUnconcluded; return m_headerTypeFlag & 0x80;
} }
/*! /*!

View File

@ -25,7 +25,7 @@ namespace TagParser {
/*! /*!
* \class TagParser::OggStream * \class TagParser::OggStream
* \brief Implementation of TagParser::AbstractTrack for Ogg streams. * \brief Implementation of TagParser::AbstractTrack for OGG streams.
*/ */
/*! /*!
@ -50,7 +50,7 @@ void OggStream::internalParseHeader(Diagnostics &diag, AbortableProgressFeedback
{ {
CPP_UTILITIES_UNUSED(progress) CPP_UTILITIES_UNUSED(progress)
static const string context("parsing Ogg page header"); static const string context("parsing OGG page header");
// read basic information from first page // read basic information from first page
OggIterator &iterator = m_container.m_iterator; OggIterator &iterator = m_container.m_iterator;
@ -218,11 +218,11 @@ void OggStream::internalParseHeader(Diagnostics &diag, AbortableProgressFeedback
hasCommentHeader = true; hasCommentHeader = true;
} else { } else {
diag.emplace_back( diag.emplace_back(
DiagLevel::Critical, "Ogg page after FLAC-to-Ogg mapping header doesn't contain Vorbis comment.", context); DiagLevel::Critical, "OGG page after FLAC-to-Ogg mapping header doesn't contain Vorbis comment.", context);
} }
} else { } else {
diag.emplace_back( diag.emplace_back(
DiagLevel::Critical, "No more Ogg pages after FLAC-to-Ogg mapping header (Vorbis comment expected).", context); DiagLevel::Critical, "No more OGG pages after FLAC-to-Ogg mapping header (Vorbis comment expected).", context);
} }
} }

View File

@ -25,15 +25,15 @@ void OpusIdentificationHeader::parseHeader(OggIterator &iterator)
{ {
char buff[19 - 8]; char buff[19 - 8];
iterator.read(buff, 8); iterator.read(buff, 8);
if (BE::toInt<std::uint64_t>(buff) != 0x4F70757348656164u) { if (BE::toUInt64(buff) != 0x4F70757348656164u) {
throw InvalidDataException(); // not Opus identification header throw InvalidDataException(); // not Opus identification header
} }
iterator.read(buff, sizeof(buff)); iterator.read(buff, sizeof(buff));
m_version = static_cast<std::uint8_t>(*(buff)); m_version = static_cast<std::uint8_t>(*(buff));
m_channels = static_cast<std::uint8_t>(*(buff + 1)); m_channels = static_cast<std::uint8_t>(*(buff + 1));
m_preSkip = LE::toInt<std::uint16_t>(buff + 2); m_preSkip = LE::toUInt16(buff + 2);
m_sampleRate = LE::toUInt32(buff + 4); m_sampleRate = LE::toUInt32(buff + 4);
m_outputGain = LE::toInt<std::uint16_t>(buff + 8); m_outputGain = LE::toUInt16(buff + 8);
m_channelMap = static_cast<std::uint8_t>(*(buff + 10)); m_channelMap = static_cast<std::uint8_t>(*(buff + 10));
} }

View File

@ -26,9 +26,7 @@ public:
PositionInSet(const StringType &numericString); PositionInSet(const StringType &numericString);
constexpr std::int32_t position() const; constexpr std::int32_t position() const;
void setPosition(std::int32_t position);
constexpr std::int32_t total() const; constexpr std::int32_t total() const;
void setTotal(std::int32_t total);
constexpr bool isNull() const; constexpr bool isNull() const;
constexpr bool operator==(const PositionInSet &other) const; constexpr bool operator==(const PositionInSet &other) const;
@ -82,14 +80,6 @@ constexpr inline std::int32_t PositionInSet::position() const
return m_position; return m_position;
} }
/*!
* \brief Sets the element position of the current instance.
*/
inline void PositionInSet::setPosition(int32_t position)
{
m_position = position;
}
/*! /*!
* \brief Returns the total element count of the current instance. * \brief Returns the total element count of the current instance.
*/ */
@ -98,14 +88,6 @@ constexpr inline std::int32_t PositionInSet::total() const
return m_total; return m_total;
} }
/*!
* \brief Sets the total element count of the current instance.
*/
inline void PositionInSet::setTotal(int32_t total)
{
m_total = total;
}
/*! /*!
* \brief Returns an indication whether both the element position and total element count is 0. * \brief Returns an indication whether both the element position and total element count is 0.
*/ */

View File

@ -13,7 +13,6 @@ namespace TagParser {
* \brief Holds 64-bit signatures. * \brief Holds 64-bit signatures.
*/ */
enum Sig64 : std::uint64_t { enum Sig64 : std::uint64_t {
ApeTag = 0x4150455441474558ul, // APETAGEX
Ar = 0x213C617263683E0A, Ar = 0x213C617263683E0A,
Asf1 = 0x3026B2758E66CF11ul, Asf1 = 0x3026B2758E66CF11ul,
Asf2 = 0xA6D900AA0062CE6Cul, Asf2 = 0xA6D900AA0062CE6Cul,
@ -115,20 +114,18 @@ ContainerFormat parseSignature(std::string_view buffer)
// read signature // read signature
std::uint64_t sig = 0; std::uint64_t sig = 0;
if (buffer.size() >= 8) { if (buffer.size() >= 8) {
sig = BE::toInt<std::uint64_t>(buffer.data()); sig = BE::toUInt64(buffer.data());
} else if (buffer.size() >= 4) { } else if (buffer.size() >= 4) {
sig = BE::toInt<std::uint32_t>(buffer.data()); sig = BE::toUInt32(buffer.data());
sig <<= 4; sig <<= 4;
} else if (buffer.size() >= 2) { } else if (buffer.size() >= 2) {
sig = BE::toInt<std::uint16_t>(buffer.data()); sig = BE::toUInt16(buffer.data());
sig <<= 6; sig <<= 6;
} else { } else {
return ContainerFormat::Unknown; return ContainerFormat::Unknown;
} }
// return corresponding container format // return corresponding container format
switch (sig) { // check 64-bit signatures switch (sig) { // check 64-bit signatures
case ApeTag:
return ContainerFormat::ApeTag;
case Ar: case Ar:
return ContainerFormat::Ar; return ContainerFormat::Ar;
case Asf1: case Asf1:
@ -194,9 +191,9 @@ ContainerFormat parseSignature(std::string_view buffer)
case PhotoshopDocument: case PhotoshopDocument:
return ContainerFormat::PhotoshopDocument; return ContainerFormat::PhotoshopDocument;
case Riff: case Riff:
if (buffer.size() >= 16 && BE::toInt<std::uint64_t>(buffer.data() + 8) == Sig64::RiffAvi) { if (buffer.size() >= 16 && BE::toUInt64(buffer.data() + 8) == Sig64::RiffAvi) {
return ContainerFormat::RiffAvi; return ContainerFormat::RiffAvi;
} else if (buffer.size() >= 12 && BE::toInt<std::uint32_t>(buffer.data() + 8) == RiffWave) { } else if (buffer.size() >= 12 && BE::toUInt32(buffer.data() + 8) == RiffWave) {
return ContainerFormat::RiffWave; return ContainerFormat::RiffWave;
} else { } else {
return ContainerFormat::Riff; return ContainerFormat::Riff;
@ -229,7 +226,7 @@ ContainerFormat parseSignature(std::string_view buffer)
case Gzip: case Gzip:
return ContainerFormat::Gzip; return ContainerFormat::Gzip;
case Id3v2: case Id3v2:
return ContainerFormat::Id3v2Tag; return ContainerFormat::Id2v2Tag;
case Utf8Text: case Utf8Text:
return ContainerFormat::Utf8Text; return ContainerFormat::Utf8Text;
} }
@ -491,10 +488,6 @@ std::string_view containerFormatName(ContainerFormat containerFormat)
return "Audio Interchange File Format"; return "Audio Interchange File Format";
case ContainerFormat::Zstd: case ContainerFormat::Zstd:
return "Zstandard compressed file"; return "Zstandard compressed file";
case ContainerFormat::Id3v2Tag:
return "ID3v2 tag";
case ContainerFormat::ApeTag:
return "APE tag";
default: default:
return "unknown"; return "unknown";
} }

View File

@ -30,7 +30,7 @@ enum class ContainerFormat : unsigned int {
Gif87a, /**< Graphics Interchange Format (1987) */ Gif87a, /**< Graphics Interchange Format (1987) */
Gif89a, /**< Graphics Interchange Format (1989) */ Gif89a, /**< Graphics Interchange Format (1989) */
Gzip, /**< gzip compressed file */ Gzip, /**< gzip compressed file */
Id3v2Tag, /**< file holding an ID3v2 tag only */ Id2v2Tag, /**< file holding an ID2v2 tag only */
Ivf, /**< IVF (simple file format that transports raw VP8/VP9/AV1 data) */ Ivf, /**< IVF (simple file format that transports raw VP8/VP9/AV1 data) */
JavaClassFile, /**< Java class file */ JavaClassFile, /**< Java class file */
Jpeg, /**< JPEG File Interchange Format */ Jpeg, /**< JPEG File Interchange Format */
@ -67,7 +67,6 @@ enum class ContainerFormat : unsigned int {
Zip, /**< ZIP archive */ Zip, /**< ZIP archive */
Aiff, /**< Audio Interchange File Format */ Aiff, /**< Audio Interchange File Format */
Zstd, /**< Zstandard-compressed data */ Zstd, /**< Zstandard-compressed data */
ApeTag, /**< APE tag */
}; };
TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, std::size_t bufferSize); TAG_PARSER_EXPORT ContainerFormat parseSignature(const char *buffer, std::size_t bufferSize);

View File

@ -4,9 +4,6 @@ using namespace std;
namespace TagParser { namespace TagParser {
/// \brief The TagPrivate struct contains private fields of the Tag class.
struct TagPrivate {};
/*! /*!
* \class TagParser::Tag * \class TagParser::Tag
* \brief The Tag class is used to store, read and write tag information. * \brief The Tag class is used to store, read and write tag information.

7
tag.h
View File

@ -8,7 +8,6 @@
#include <c++utilities/io/binaryreader.h> #include <c++utilities/io/binaryreader.h>
#include <cstdint> #include <cstdint>
#include <memory>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@ -126,7 +125,6 @@ enum class KnownField : unsigned int {
ProductionCopyright, /** production copyright */ ProductionCopyright, /** production copyright */
License, /** license */ License, /** license */
TermsOfUse, /** terms of use */ TermsOfUse, /** terms of use */
PublisherWebpage, /** the publisher's official webpage */
}; };
/*! /*!
@ -137,7 +135,7 @@ constexpr KnownField firstKnownField = KnownField::Title;
/*! /*!
* \brief The last valid entry in the TagParser::KnownField enum. * \brief The last valid entry in the TagParser::KnownField enum.
*/ */
constexpr KnownField lastKnownField = KnownField::PublisherWebpage; constexpr KnownField lastKnownField = KnownField::TermsOfUse;
/*! /*!
* \brief The number of valid entries in the TagParser::KnownField enum. * \brief The number of valid entries in the TagParser::KnownField enum.
@ -162,8 +160,6 @@ constexpr KnownField nextKnownField(KnownField field)
return isKnownFieldDeprecated(next) ? nextKnownField(next) : next; return isKnownFieldDeprecated(next) ? nextKnownField(next) : next;
} }
struct TagPrivate;
class TAG_PARSER_EXPORT Tag { class TAG_PARSER_EXPORT Tag {
public: public:
virtual ~Tag(); virtual ~Tag();
@ -203,7 +199,6 @@ protected:
std::string m_version; std::string m_version;
std::uint64_t m_size; std::uint64_t m_size;
std::unique_ptr<TagPrivate> m_p;
TagTarget m_target; TagTarget m_target;
}; };

View File

@ -22,9 +22,6 @@ using namespace CppUtilities;
namespace TagParser { namespace TagParser {
/// \brief The TagValuePrivate struct contains private fields of the TagValue class.
struct TagValuePrivate {};
/*! /*!
* \brief Returns the string representation of the specified \a dataType. * \brief Returns the string representation of the specified \a dataType.
*/ */
@ -101,7 +98,7 @@ pair<const char *, float> encodingParameter(TagTextEncoding tagTextEncoding)
* *
* Values of the type TagDataType::Text can be differently encoded. * Values of the type TagDataType::Text can be differently encoded.
* - See TagParser::TagTextEncoding for a list of encodings supported by this library. * - See TagParser::TagTextEncoding for a list of encodings supported by this library.
* - Tag formats usually only support a subset of these encodings. The serializers for the various tag * - Tag formats usually only support a subset of these encodings. The serializers for the varoius tag
* formats provided by this library will keep the encoding if possible and otherwise convert the assigned * formats provided by this library will keep the encoding if possible and otherwise convert the assigned
* text to an encoding supported by the tag format on the fly. Note that ID3v1 does not specify which * text to an encoding supported by the tag format on the fly. Note that ID3v1 does not specify which
* encodings are supported (or unsupported) so the serializer will just write text data as-is. * encodings are supported (or unsupported) so the serializer will just write text data as-is.
@ -148,140 +145,6 @@ TagValue::TagValue(const TagValue &other)
} }
} }
TagValue::TagValue(TagValue &&other) = default;
/*!
* \brief Constructs an empty TagValue.
*/
TagValue::TagValue()
: m_size(0)
, m_type(TagDataType::Undefined)
, m_encoding(TagTextEncoding::Latin1)
, m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
}
/*!
* \brief Constructs a new TagValue holding a copy of the given \a text.
* \param text Specifies the text to be assigned.
* \param textSize Specifies the size of \a text. (The actual number of bytes, not the number of characters.)
* \param textEncoding Specifies the encoding of the given \a text.
* \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to
* use \a textEncoding without any character set conversions.
* \throws Throws a ConversionException if the conversion the specified character set fails.
* \remarks Strips the BOM of the specified \a text.
*/
TagValue::TagValue(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
: m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
assignText(text, textSize, textEncoding, convertTo);
}
/*!
* \brief Constructs a new TagValue holding a copy of the given \a text.
* \param text Specifies the text to be assigned. This string must be null-terminated.
* \param textEncoding Specifies the encoding of the given \a text.
* \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to
* use \a textEncoding without any character set conversions.
* \throws Throws a ConversionException if the conversion the specified character set fails.
* \remarks Strips the BOM of the specified \a text.
*/
TagValue::TagValue(const char *text, TagTextEncoding textEncoding, TagTextEncoding convertTo)
{
assignText(text, std::strlen(text), textEncoding, convertTo);
}
/*!
* \brief Constructs a new TagValue holding a copy of the given \a text.
* \param text Specifies the text to be assigned.
* \param textEncoding Specifies the encoding of the given \a text.
* \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to
* use \a textEncoding without any character set conversions.
* \throws Throws a ConversionException if the conversion the specified character set fails.
* \remarks Strips the BOM of the specified \a text.
*/
TagValue::TagValue(const std::string &text, TagTextEncoding textEncoding, TagTextEncoding convertTo)
: m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
assignText(text, textEncoding, convertTo);
}
/*!
* \brief Constructs a new TagValue holding a copy of the given \a text.
* \param text Specifies the text to be assigned.
* \param textEncoding Specifies the encoding of the given \a text.
* \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to
* use \a textEncoding without any character set conversions.
* \throws Throws a ConversionException if the conversion the specified character set fails.
* \remarks Strips the BOM of the specified \a text.
*/
TagValue::TagValue(std::string_view text, TagTextEncoding textEncoding, TagTextEncoding convertTo)
: m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
assignText(text, textEncoding, convertTo);
}
/*!
* \brief Destroys the TagValue.
*/
TagValue::~TagValue()
{
}
/*!
* \brief Constructs a new TagValue with a copy of the given \a data.
*
* \param data Specifies a pointer to the data.
* \param length Specifies the length of the data.
* \param type Specifies the type of the data as TagDataType.
* \param encoding Specifies the encoding of the data as TagTextEncoding. The
* encoding will only be considered if a text is assigned.
* \remarks Strips the BOM of the specified \a data if \a type is TagDataType::Text.
*/
TagValue::TagValue(const char *data, std::size_t length, TagDataType type, TagTextEncoding encoding)
: m_size(length)
, m_type(type)
, m_encoding(encoding)
, m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
if (length) {
if (type == TagDataType::Text) {
stripBom(data, m_size, encoding);
}
m_ptr = std::make_unique<char[]>(m_size);
std::copy(data, data + m_size, m_ptr.get());
}
}
/*!
* \brief Constructs a new TagValue holding with the given \a data.
*
* The \a data is not copied. It is moved.
*
* \param data Specifies a pointer to the data.
* \param length Specifies the length of the data.
* \param type Specifies the type of the data as TagDataType.
* \param encoding Specifies the encoding of the data as TagTextEncoding. The
* encoding will only be considered if a text is assigned.
* \remarks Does not strip the BOM so for consistency the caller must ensure there is no BOM present.
*/
TagValue::TagValue(std::unique_ptr<char[]> &&data, std::size_t length, TagDataType type, TagTextEncoding encoding)
: m_size(length)
, m_type(type)
, m_encoding(encoding)
, m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
if (length) {
m_ptr = std::move(data);
}
}
/*! /*!
* \brief Assigns the value of another TagValue to the current instance. * \brief Assigns the value of another TagValue to the current instance.
*/ */
@ -307,8 +170,6 @@ TagValue &TagValue::operator=(const TagValue &other)
return *this; return *this;
} }
TagValue &TagValue::operator=(TagValue &&other) = default;
/// \cond /// \cond
TagTextEncoding pickUtfEncoding(TagTextEncoding encoding1, TagTextEncoding encoding2) TagTextEncoding pickUtfEncoding(TagTextEncoding encoding1, TagTextEncoding encoding2)
{ {
@ -743,7 +604,7 @@ TimeSpan TagValue::toTimeSpan() const
switch (m_size) { switch (m_size) {
case sizeof(std::uint64_t): { case sizeof(std::uint64_t): {
const auto ticks = *(reinterpret_cast<std::uint64_t *>(m_ptr.get())); const auto ticks = *(reinterpret_cast<std::uint64_t *>(m_ptr.get()));
if (ticks < static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { if (ticks < std::numeric_limits<std::int64_t>::max()) {
return TimeSpan(static_cast<std::int64_t>(ticks)); return TimeSpan(static_cast<std::int64_t>(ticks));
} }
} }
@ -1360,13 +1221,13 @@ void TagValue::stripBom(const char *&text, size_t &length, TagTextEncoding encod
} }
break; break;
case TagTextEncoding::Utf16LittleEndian: case TagTextEncoding::Utf16LittleEndian:
if ((length >= 2) && (LE::toInt<std::uint16_t>(text) == 0xFEFF)) { if ((length >= 2) && (LE::toUInt16(text) == 0xFEFF)) {
text += 2; text += 2;
length -= 2; length -= 2;
} }
break; break;
case TagTextEncoding::Utf16BigEndian: case TagTextEncoding::Utf16BigEndian:
if ((length >= 2) && (BE::toInt<std::uint16_t>(text) == 0xFEFF)) { if ((length >= 2) && (BE::toUInt16(text) == 0xFEFF)) {
text += 2; text += 2;
length -= 2; length -= 2;
} }

View File

@ -131,8 +131,6 @@ enum class TagDataType : unsigned int {
DateTimeExpression, /**< date time expression, see CppUtilities::DateTimeExpression */ DateTimeExpression, /**< date time expression, see CppUtilities::DateTimeExpression */
}; };
TAG_PARSER_EXPORT std::string_view tagDataTypeString(TagDataType dataType);
/*! /*!
* \brief The TagValueComparisionOption enum specifies options for TagValue::compareTo(). * \brief The TagValueComparisionOption enum specifies options for TagValue::compareTo().
*/ */
@ -142,8 +140,6 @@ enum class TagValueComparisionFlags : unsigned int {
IgnoreMetaData = 0x2, /**< do *not* take meta-data like description and MIME-types into account */ IgnoreMetaData = 0x2, /**< do *not* take meta-data like description and MIME-types into account */
}; };
struct TagValuePrivate;
class TAG_PARSER_EXPORT TagValue { class TAG_PARSER_EXPORT TagValue {
public: public:
// constructor, destructor // constructor, destructor
@ -168,12 +164,12 @@ public:
explicit TagValue(CppUtilities::TimeSpan value); explicit TagValue(CppUtilities::TimeSpan value);
explicit TagValue(const Popularity &value); explicit TagValue(const Popularity &value);
TagValue(const TagValue &other); TagValue(const TagValue &other);
TagValue(TagValue &&other); TagValue(TagValue &&other) = default;
~TagValue(); ~TagValue();
// operators // operators
TagValue &operator=(const TagValue &other); TagValue &operator=(const TagValue &other);
TagValue &operator=(TagValue &&other); TagValue &operator=(TagValue &&other) = default;
bool operator==(const TagValue &other) const; bool operator==(const TagValue &other) const;
bool operator!=(const TagValue &other) const; bool operator!=(const TagValue &other) const;
operator bool() const; operator bool() const;
@ -264,9 +260,90 @@ private:
TagTextEncoding m_encoding; TagTextEncoding m_encoding;
TagTextEncoding m_descEncoding; TagTextEncoding m_descEncoding;
TagValueFlags m_flags; TagValueFlags m_flags;
std::unique_ptr<TagValuePrivate> m_p;
}; };
/*!
* \brief Constructs an empty TagValue.
*/
inline TagValue::TagValue()
: m_size(0)
, m_type(TagDataType::Undefined)
, m_encoding(TagTextEncoding::Latin1)
, m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
}
/*!
* \brief Destroys the TagValue.
*/
inline TagValue::~TagValue()
{
}
/*!
* \brief Constructs a new TagValue holding a copy of the given \a text.
* \param text Specifies the text to be assigned.
* \param textSize Specifies the size of \a text. (The actual number of bytes, not the number of characters.)
* \param textEncoding Specifies the encoding of the given \a text.
* \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to
* use \a textEncoding without any character set conversions.
* \throws Throws a ConversionException if the conversion the specified character set fails.
* \remarks Strips the BOM of the specified \a text.
*/
inline TagValue::TagValue(const char *text, std::size_t textSize, TagTextEncoding textEncoding, TagTextEncoding convertTo)
: m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
assignText(text, textSize, textEncoding, convertTo);
}
/*!
* \brief Constructs a new TagValue holding a copy of the given \a text.
* \param text Specifies the text to be assigned. This string must be null-terminated.
* \param textEncoding Specifies the encoding of the given \a text.
* \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to
* use \a textEncoding without any character set conversions.
* \throws Throws a ConversionException if the conversion the specified character set fails.
* \remarks Strips the BOM of the specified \a text.
*/
inline TagValue::TagValue(const char *text, TagTextEncoding textEncoding, TagTextEncoding convertTo)
{
assignText(text, std::strlen(text), textEncoding, convertTo);
}
/*!
* \brief Constructs a new TagValue holding a copy of the given \a text.
* \param text Specifies the text to be assigned.
* \param textEncoding Specifies the encoding of the given \a text.
* \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to
* use \a textEncoding without any character set conversions.
* \throws Throws a ConversionException if the conversion the specified character set fails.
* \remarks Strips the BOM of the specified \a text.
*/
inline TagValue::TagValue(const std::string &text, TagTextEncoding textEncoding, TagTextEncoding convertTo)
: m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
assignText(text, textEncoding, convertTo);
}
/*!
* \brief Constructs a new TagValue holding a copy of the given \a text.
* \param text Specifies the text to be assigned.
* \param textEncoding Specifies the encoding of the given \a text.
* \param convertTo Specifies the encoding to convert \a text to; set to TagTextEncoding::Unspecified to
* use \a textEncoding without any character set conversions.
* \throws Throws a ConversionException if the conversion the specified character set fails.
* \remarks Strips the BOM of the specified \a text.
*/
inline TagValue::TagValue(std::string_view text, TagTextEncoding textEncoding, TagTextEncoding convertTo)
: m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
assignText(text, textEncoding, convertTo);
}
/*! /*!
* \brief Constructs a new TagValue holding the given integer \a value. * \brief Constructs a new TagValue holding the given integer \a value.
*/ */
@ -283,6 +360,56 @@ inline TagParser::TagValue::TagValue(std::uint64_t value)
{ {
} }
/*!
* \brief Constructs a new TagValue with a copy of the given \a data.
*
* \param data Specifies a pointer to the data.
* \param length Specifies the length of the data.
* \param type Specifies the type of the data as TagDataType.
* \param encoding Specifies the encoding of the data as TagTextEncoding. The
* encoding will only be considered if a text is assigned.
* \remarks Strips the BOM of the specified \a data if \a type is TagDataType::Text.
*/
inline TagValue::TagValue(const char *data, std::size_t length, TagDataType type, TagTextEncoding encoding)
: m_size(length)
, m_type(type)
, m_encoding(encoding)
, m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
if (length) {
if (type == TagDataType::Text) {
stripBom(data, m_size, encoding);
}
m_ptr = std::make_unique<char[]>(m_size);
std::copy(data, data + m_size, m_ptr.get());
}
}
/*!
* \brief Constructs a new TagValue holding with the given \a data.
*
* The \a data is not copied. It is moved.
*
* \param data Specifies a pointer to the data.
* \param length Specifies the length of the data.
* \param type Specifies the type of the data as TagDataType.
* \param encoding Specifies the encoding of the data as TagTextEncoding. The
* encoding will only be considered if a text is assigned.
* \remarks Does not strip the BOM so for consistency the caller must ensure there is no BOM present.
*/
inline TagValue::TagValue(std::unique_ptr<char[]> &&data, std::size_t length, TagDataType type, TagTextEncoding encoding)
: m_size(length)
, m_type(type)
, m_encoding(encoding)
, m_descEncoding(TagTextEncoding::Latin1)
, m_flags(TagValueFlags::None)
{
if (length) {
m_ptr = std::move(data);
}
}
/*! /*!
* \brief Constructs a new TagValue holding a copy of the given PositionInSet \a value. * \brief Constructs a new TagValue holding a copy of the given PositionInSet \a value.
*/ */

View File

@ -50,7 +50,6 @@ class OverallTests : public TestFixture {
CPPUNIT_TEST(testFlacMaking); CPPUNIT_TEST(testFlacMaking);
CPPUNIT_TEST(testMkvMakingWithDifferentSettings); CPPUNIT_TEST(testMkvMakingWithDifferentSettings);
CPPUNIT_TEST(testMkvMakingNestedTags); CPPUNIT_TEST(testMkvMakingNestedTags);
CPPUNIT_TEST(testVorbisCommentFieldHandling);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
public: public:
@ -123,7 +122,6 @@ public:
void testMp3Making(); void testMp3Making();
void testOggMaking(); void testOggMaking();
void testFlacMaking(); void testFlacMaking();
void testVorbisCommentFieldHandling();
private: private:
MediaFileInfo m_fileInfo; MediaFileInfo m_fileInfo;

View File

@ -35,7 +35,7 @@ void OverallTests::checkFlacTestfile1()
CPPUNIT_ASSERT_EQUAL("1998"s, tags.front()->value(KnownField::RecordDate).toString()); CPPUNIT_ASSERT_EQUAL("1998"s, tags.front()->value(KnownField::RecordDate).toString());
CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).isEmpty()); CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).isEmpty());
//CPPUNIT_ASSERT(tags.front()->value(KnownField::Cover).dataSize() == 0x58f3); //CPPUNIT_ASSERT(tags.front()->value(KnownField::Cover).dataSize() == 0x58f3);
//CPPUNIT_ASSERT(BE::toInt<std::uint64_t>(tags.front()->value(KnownField::Cover).dataPointer()) == 0xFFD8FFE000104A46); //CPPUNIT_ASSERT(BE::toUInt64(tags.front()->value(KnownField::Cover).dataPointer()) == 0xFFD8FFE000104A46);
CPPUNIT_ASSERT_EQUAL(PositionInSet(3, 4), tags.front()->value(KnownField::TrackPosition).toPositionInSet()); CPPUNIT_ASSERT_EQUAL(PositionInSet(3, 4), tags.front()->value(KnownField::TrackPosition).toPositionInSet());
CPPUNIT_ASSERT_EQUAL(PositionInSet(1, 1), tags.front()->value(KnownField::DiskPosition).toPositionInSet()); CPPUNIT_ASSERT_EQUAL(PositionInSet(1, 1), tags.front()->value(KnownField::DiskPosition).toPositionInSet());
break; break;
@ -83,7 +83,7 @@ void OverallTests::checkFlacTestfile2()
bool gotMessageAboutMissingVorbisComment = false; bool gotMessageAboutMissingVorbisComment = false;
for (const auto &msg : m_diag) { for (const auto &msg : m_diag) {
if (msg.level() == DiagLevel::Critical) { if (msg.level() == DiagLevel::Critical) {
CPPUNIT_ASSERT_EQUAL("Ogg page after FLAC-to-Ogg mapping header doesn't contain Vorbis comment."s, msg.message()); CPPUNIT_ASSERT_EQUAL("OGG page after FLAC-to-Ogg mapping header doesn't contain Vorbis comment."s, msg.message());
gotMessageAboutMissingVorbisComment = true; gotMessageAboutMissingVorbisComment = true;
continue; continue;
} }

View File

@ -37,7 +37,7 @@ enum TestFlag {
void OverallTests::checkMkvTestfile1() void OverallTests::checkMkvTestfile1()
{ {
CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat()); CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromMinutes(1.0) + TimeSpan::fromSeconds(27.0) + TimeSpan::fromMilliseconds(336.0), m_fileInfo.duration()); CPPUNIT_ASSERT_EQUAL(TimeSpan::fromMinutes(1) + TimeSpan::fromSeconds(27) + TimeSpan::fromMilliseconds(336), m_fileInfo.duration());
const auto tracks = m_fileInfo.tracks(); const auto tracks = m_fileInfo.tracks();
CPPUNIT_ASSERT_EQUAL(2_st, tracks.size()); CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
for (const auto &track : tracks) { for (const auto &track : tracks) {
@ -86,7 +86,7 @@ void OverallTests::checkMkvTestfile1()
void OverallTests::checkMkvTestfile2() void OverallTests::checkMkvTestfile2()
{ {
CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat()); CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(47.0) + TimeSpan::fromMilliseconds(509.0), m_fileInfo.duration()); CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(47) + TimeSpan::fromMilliseconds(509), m_fileInfo.duration());
const auto tracks = m_fileInfo.tracks(); const auto tracks = m_fileInfo.tracks();
CPPUNIT_ASSERT_EQUAL(2_st, tracks.size()); CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
for (const auto &track : tracks) { for (const auto &track : tracks) {
@ -135,7 +135,7 @@ void OverallTests::checkMkvTestfile2()
void OverallTests::checkMkvTestfile3() void OverallTests::checkMkvTestfile3()
{ {
CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat()); CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(49.0) + TimeSpan::fromMilliseconds(64.0), m_fileInfo.duration()); CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(49) + TimeSpan::fromMilliseconds(64), m_fileInfo.duration());
const auto tracks = m_fileInfo.tracks(); const auto tracks = m_fileInfo.tracks();
CPPUNIT_ASSERT_EQUAL(2_st, tracks.size()); CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
for (const auto &track : tracks) { for (const auto &track : tracks) {
@ -244,7 +244,7 @@ void OverallTests::checkMkvTestfile4()
void OverallTests::checkMkvTestfile5() void OverallTests::checkMkvTestfile5()
{ {
CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat()); CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(46.0) + TimeSpan::fromMilliseconds(665.0), m_fileInfo.duration()); CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(46) + TimeSpan::fromMilliseconds(665), m_fileInfo.duration());
const auto tracks = m_fileInfo.tracks(); const auto tracks = m_fileInfo.tracks();
CPPUNIT_ASSERT_EQUAL(11_st, tracks.size()); CPPUNIT_ASSERT_EQUAL(11_st, tracks.size());
for (const auto &track : tracks) { for (const auto &track : tracks) {
@ -298,7 +298,7 @@ void OverallTests::checkMkvTestfile5()
void OverallTests::checkMkvTestfile6() void OverallTests::checkMkvTestfile6()
{ {
CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat()); CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromMinutes(1.0) + TimeSpan::fromSeconds(27.0) + TimeSpan::fromMilliseconds(336.0), m_fileInfo.duration()); CPPUNIT_ASSERT_EQUAL(TimeSpan::fromMinutes(1) + TimeSpan::fromSeconds(27) + TimeSpan::fromMilliseconds(336), m_fileInfo.duration());
const auto tracks = m_fileInfo.tracks(); const auto tracks = m_fileInfo.tracks();
CPPUNIT_ASSERT_EQUAL(2_st, tracks.size()); CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
for (const auto &track : tracks) { for (const auto &track : tracks) {
@ -348,7 +348,7 @@ void OverallTests::checkMkvTestfile6()
void OverallTests::checkMkvTestfile7() void OverallTests::checkMkvTestfile7()
{ {
CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat()); CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(37.0) + TimeSpan::fromMilliseconds(43.0), m_fileInfo.duration()); CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(37) + TimeSpan::fromMilliseconds(43), m_fileInfo.duration());
const auto tracks = m_fileInfo.tracks(); const auto tracks = m_fileInfo.tracks();
CPPUNIT_ASSERT_EQUAL(2_st, tracks.size()); CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
for (const auto &track : tracks) { for (const auto &track : tracks) {
@ -408,7 +408,7 @@ void OverallTests::checkMkvTestfile7()
void OverallTests::checkMkvTestfile8() void OverallTests::checkMkvTestfile8()
{ {
CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat()); CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(47.0) + TimeSpan::fromMilliseconds(341.0), m_fileInfo.duration()); CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(47) + TimeSpan::fromMilliseconds(341), m_fileInfo.duration());
const auto tracks = m_fileInfo.tracks(); const auto tracks = m_fileInfo.tracks();
CPPUNIT_ASSERT_EQUAL(2_st, tracks.size()); CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
for (const auto &track : tracks) { for (const auto &track : tracks) {
@ -459,7 +459,7 @@ void OverallTests::checkMkvTestfile8()
void OverallTests::checkMkvTestfileHandbrakeChapters() void OverallTests::checkMkvTestfileHandbrakeChapters()
{ {
CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat()); CPPUNIT_ASSERT_EQUAL(ContainerFormat::Matroska, m_fileInfo.containerFormat());
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(27.0) + TimeSpan::fromMilliseconds(569.0), m_fileInfo.duration()); CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(27) + TimeSpan::fromMilliseconds(569), m_fileInfo.duration());
const auto tracks = m_fileInfo.tracks(); const auto tracks = m_fileInfo.tracks();
CPPUNIT_ASSERT_EQUAL(2_st, tracks.size()); CPPUNIT_ASSERT_EQUAL(2_st, tracks.size());
for (const auto &track : tracks) { for (const auto &track : tracks) {

View File

@ -209,7 +209,7 @@ void OverallTests::checkMp4Testfile4()
CPPUNIT_ASSERT_EQUAL("1998"s, tags.front()->value(KnownField::RecordDate).toString()); CPPUNIT_ASSERT_EQUAL("1998"s, tags.front()->value(KnownField::RecordDate).toString());
CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).isEmpty()); CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).isEmpty());
CPPUNIT_ASSERT_EQUAL(0x58f3_st, tags.front()->value(KnownField::Cover).dataSize()); CPPUNIT_ASSERT_EQUAL(0x58f3_st, tags.front()->value(KnownField::Cover).dataSize());
CPPUNIT_ASSERT_EQUAL(0xFFD8FFE000104A46ul, BE::toInt<std::uint64_t>(tags.front()->value(KnownField::Cover).dataPointer())); CPPUNIT_ASSERT_EQUAL(0xFFD8FFE000104A46ul, BE::toUInt64(tags.front()->value(KnownField::Cover).dataPointer()));
CPPUNIT_ASSERT_EQUAL(PositionInSet(3, 4), tags.front()->value(KnownField::TrackPosition).toPositionInSet()); CPPUNIT_ASSERT_EQUAL(PositionInSet(3, 4), tags.front()->value(KnownField::TrackPosition).toPositionInSet());
CPPUNIT_ASSERT_EQUAL(PositionInSet(1, 1), tags.front()->value(KnownField::DiskPosition).toPositionInSet()); CPPUNIT_ASSERT_EQUAL(PositionInSet(1, 1), tags.front()->value(KnownField::DiskPosition).toPositionInSet());
break; break;

View File

@ -4,12 +4,10 @@
#include "../abstracttrack.h" #include "../abstracttrack.h"
#include "../tag.h" #include "../tag.h"
#include "../vorbis/vorbiscomment.h" #include "../vorbis/vorbiscomment.h"
#include "../vorbis/vorbiscommentfield.h"
#include "../vorbis/vorbiscommentids.h"
#include <c++utilities/io/misc.h> #include <c++utilities/io/misc.h>
#include <algorithm> #include <functional>
using namespace CppUtilities; using namespace CppUtilities;
@ -137,19 +135,17 @@ void OverallTests::checkOggTestfile3()
return; return;
} }
CPPUNIT_ASSERT_EQUAL_MESSAGE("warning present", DiagLevel::Warning, m_diag.level()); CPPUNIT_ASSERT_EQUAL_MESSAGE("warning present", DiagLevel::Warning, m_diag.level());
CPPUNIT_ASSERT(std::find_if(m_diag.begin(), m_diag.end(), [](const auto &msg) { for (const auto &msg : m_diag) {
return startsWith(msg.message(), "3 bytes left in last segment"); if (msg.level() == DiagLevel::Warning) {
}) != m_diag.end()); CPPUNIT_ASSERT_EQUAL_MESSAGE("warning due to broken segment termination", "3 bytes left in last segment."s, msg.message());
CPPUNIT_ASSERT(std::find_if(m_diag.begin(), m_diag.end(), [](const auto &msg) { CPPUNIT_ASSERT_EQUAL_MESSAGE("warning relates to Vorbis comment", "parsing Vorbis comment"s, msg.context());
return startsWith(msg.message(), "Tag spans over 6 pages but absolute granule position of unfinished page at"); break;
}) != m_diag.end()); }
CPPUNIT_ASSERT(std::find_if(m_diag.begin(), m_diag.end(), [](const auto &msg) { }
return startsWith(msg.message(), "The tag is continued in Ogg page at");
}) != m_diag.end());
} }
/*! /*!
* \brief Checks whether test meta data for Ogg files has been applied correctly. * \brief Checks whether test meta data for OGG files has been applied correctly.
*/ */
void OverallTests::checkOggTestMetaData() void OverallTests::checkOggTestMetaData()
{ {
@ -210,7 +206,7 @@ void OverallTests::setOggTestMetaDataCover()
*/ */
void OverallTests::testOggParsing() void OverallTests::testOggParsing()
{ {
cerr << endl << "Ogg parser" << endl; cerr << endl << "OGG parser" << endl;
m_fileInfo.setForceFullParse(false); m_fileInfo.setForceFullParse(false);
m_tagStatus = TagStatus::Original; m_tagStatus = TagStatus::Original;
parseFile(testFilePath("mtx-test-data/ogg/qt4dance_medium.ogg"), &OverallTests::checkOggTestfile1); parseFile(testFilePath("mtx-test-data/ogg/qt4dance_medium.ogg"), &OverallTests::checkOggTestfile1);
@ -243,7 +239,7 @@ void OverallTests::testOggMaking()
} else { } else {
testConditions.emplace_back("modifying tag"); testConditions.emplace_back("modifying tag");
} }
cerr << endl << "Ogg maker - testmode " << m_mode << ": " << joinStrings(testConditions, ", ") << endl; cerr << endl << "OGG maker - testmode " << m_mode << ": " << joinStrings(testConditions, ", ") << endl;
// do actual tests // do actual tests
m_tagStatus = (m_mode & RemoveTag) ? TagStatus::Removed : TagStatus::TestMetaDataPresent; m_tagStatus = (m_mode & RemoveTag) ? TagStatus::Removed : TagStatus::TestMetaDataPresent;
@ -254,52 +250,3 @@ void OverallTests::testOggMaking()
makeFile(workingCopyPath("ogg/noise-without-cover.opus"), modifyRoutineCover, &OverallTests::checkOggTestfile3); makeFile(workingCopyPath("ogg/noise-without-cover.opus"), modifyRoutineCover, &OverallTests::checkOggTestfile3);
} }
} }
/*!
* \brief Tests the Vorbis Comment specifc handling of certain fields done in VorbisComment::convertTotalFields().
*/
void OverallTests::testVorbisCommentFieldHandling()
{
const auto context = std::string();
const auto trackNumberFieldId = std::string(VorbisCommentIds::trackNumber());
const auto trackTotalFieldId = std::string(VorbisCommentIds::trackTotal());
const auto diskNumberFieldId = std::string(VorbisCommentIds::diskNumber());
const auto diskTotalFieldId = std::string(VorbisCommentIds::diskTotal());
auto diag = Diagnostics();
auto vc = VorbisComment();
auto trackNumber = VorbisCommentField(trackNumberFieldId, TagValue(5));
auto trackTotal = VorbisCommentField(trackTotalFieldId, TagValue(20));
auto &fields = vc.fields();
fields.insert(std::make_pair(trackNumberFieldId, std::move(trackNumber)));
fields.insert(std::make_pair(trackTotalFieldId, std::move(trackTotal)));
vc.convertTotalFields(context, diag);
const auto convertedValues = vc.values(trackNumberFieldId);
CPPUNIT_ASSERT_EQUAL_MESSAGE("the two fileds have been combined into one", 1_st, fields.size());
CPPUNIT_ASSERT_EQUAL_MESSAGE("there is exactly one track number value", 1_st, convertedValues.size());
const auto convertedTrackNumber = convertedValues.front()->toPositionInSet();
CPPUNIT_ASSERT_EQUAL(PositionInSet(5, 20), convertedTrackNumber);
CPPUNIT_ASSERT_EQUAL(0_st, diag.size());
auto diskNumber = VorbisCommentField(diskNumberFieldId, TagValue("invalid pos"));
auto diskTotal = VorbisCommentField(diskTotalFieldId, TagValue("invalid total"));
auto diskTotal2 = VorbisCommentField(diskTotalFieldId, TagValue(42));
fields.insert(std::make_pair(diskNumberFieldId, std::move(diskNumber)));
fields.insert(std::make_pair(diskTotalFieldId, std::move(diskTotal)));
fields.insert(std::make_pair(diskTotalFieldId, std::move(diskTotal2)));
vc.convertTotalFields(context, diag);
const auto newDiskNumberValues = vc.values(diskNumberFieldId);
const auto newDiskTotalValues = vc.values(diskTotalFieldId);
CPPUNIT_ASSERT_EQUAL_MESSAGE("invalid fields have not been combined", 4_st, fields.size());
CPPUNIT_ASSERT_EQUAL_MESSAGE("invalid disk position has been preserved and valid disk total converted", 2_st, newDiskNumberValues.size());
CPPUNIT_ASSERT_EQUAL_MESSAGE("invalid disk total has been preserved", 1_st, newDiskTotalValues.size());
const auto preservedDiskNumber = newDiskNumberValues[0]->toString();
const auto convertedDiskTotal = newDiskNumberValues[1]->toPositionInSet();
const auto preservedDiskTotal = newDiskTotalValues[0]->toString();
CPPUNIT_ASSERT_EQUAL("invalid pos"s, preservedDiskNumber);
CPPUNIT_ASSERT_EQUAL(PositionInSet(0, 42), convertedDiskTotal);
CPPUNIT_ASSERT_EQUAL("invalid total"s, preservedDiskTotal);
CPPUNIT_ASSERT_EQUAL(3_st, diag.size());
}

View File

@ -157,7 +157,7 @@ void TagValueTests::testPositionInSet()
void TagValueTests::testTimeSpan() void TagValueTests::testTimeSpan()
{ {
const TimeSpan fiveMinutes(TimeSpan::fromMinutes(5.0)); const TimeSpan fiveMinutes(TimeSpan::fromMinutes(5));
TagValue timeSpan; TagValue timeSpan;
timeSpan.assignTimeSpan(fiveMinutes); timeSpan.assignTimeSpan(fiveMinutes);
CPPUNIT_ASSERT_EQUAL(timeSpan, TagValue(timeSpan)); CPPUNIT_ASSERT_EQUAL(timeSpan, TagValue(timeSpan));

View File

@ -82,7 +82,6 @@ struct TestFile {
{ "ogg/example-cover.png", { "897e1a2d0cfb79c1fe5068108bb34610c3758bd0b9a7e90c1702c4e6972e0801" } }, { "ogg/example-cover.png", { "897e1a2d0cfb79c1fe5068108bb34610c3758bd0b9a7e90c1702c4e6972e0801" } },
}; };
/// \cond
struct EvpMdCtx { struct EvpMdCtx {
EvpMdCtx() EvpMdCtx()
: handle(EVP_MD_CTX_new()) : handle(EVP_MD_CTX_new())
@ -96,7 +95,6 @@ struct EvpMdCtx {
} }
EVP_MD_CTX *handle; EVP_MD_CTX *handle;
}; };
/// \endcond
/*! /*!
* \brief Computes the SHA-256 checksums for the file using OpenSSL. * \brief Computes the SHA-256 checksums for the file using OpenSSL.

View File

@ -107,10 +107,6 @@ VorbisComment::IdentifierType VorbisComment::internallyGetFieldId(KnownField fie
return std::string(rating()); return std::string(rating());
case KnownField::Bpm: case KnownField::Bpm:
return std::string(bpm()); return std::string(bpm());
case KnownField::Publisher:
return std::string(publisher());
case KnownField::PublisherWebpage:
return std::string(publisherWebpage());
default: default:
return std::string(); return std::string();
} }
@ -150,80 +146,16 @@ KnownField VorbisComment::internallyGetKnownField(const IdentifierType &id) cons
{ isrc(), KnownField::ISRC }, { isrc(), KnownField::ISRC },
{ rating(), KnownField::Rating }, { rating(), KnownField::Rating },
{ bpm(), KnownField::Bpm }, { bpm(), KnownField::Bpm },
{ publisher(), KnownField::Publisher },
{ publisherWebpage(), KnownField::PublisherWebpage },
}); });
// clang-format on // clang-format on
const auto knownField(fieldMap.find(id)); const auto knownField(fieldMap.find(id));
return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid; return knownField != fieldMap.cend() ? knownField->second : KnownField::Invalid;
} }
/// \cond
void VorbisComment::extendPositionInSetField(std::string_view field, std::string_view totalField, const std::string &diagContext, Diagnostics &diag)
{
auto totalValues = std::vector<std::int32_t>();
auto fieldsIter = fields().equal_range(std::string(totalField));
auto fieldsDist = std::distance(fieldsIter.first, fieldsIter.second);
if (!fieldsDist) {
return;
}
totalValues.reserve(static_cast<std::size_t>(fieldsDist));
for (; fieldsIter.first != fieldsIter.second;) {
try {
totalValues.emplace_back(fieldsIter.first->second.value().toInteger());
fields().erase(fieldsIter.first++);
} catch (const ConversionException &e) {
diag.emplace_back(DiagLevel::Warning, argsToString("Unable to parse \"", totalField, "\" as integer: ", e.what()), diagContext);
totalValues.emplace_back(0);
++fieldsIter.first;
}
}
auto totalIter = totalValues.begin(), totalEnd = totalValues.end();
for (fieldsIter = fields().equal_range(std::string(field)); fieldsIter.first != fieldsIter.second && totalIter != totalEnd;
++fieldsIter.first, ++totalIter) {
auto &v = fieldsIter.first->second.value();
try {
auto p = v.toPositionInSet();
if (p.total() && p.total() != *totalIter) {
diag.emplace_back(DiagLevel::Warning,
argsToString("The \"", totalField, "\" field value (", *totalIter, ") does not match \"", field, "\" field value (", p.total(),
"). Discarding the former in favor of the latter."),
diagContext);
} else {
p.setTotal(*totalIter);
v.assignPosition(p);
}
} catch (const ConversionException &e) {
diag.emplace_back(DiagLevel::Warning,
argsToString("Unable to parse \"", field, "\" as position in set for incorporating \"", totalField, "\": ", e.what()), diagContext);
}
}
if (totalIter != totalEnd) {
diag.emplace_back(
DiagLevel::Warning, argsToString("Vorbis Comment contains more \"", totalField, "\" fields than \"", field, "\" fields."), diagContext);
}
for (; totalIter != totalEnd; ++totalIter) {
fields().insert(std::make_pair(field, VorbisCommentField(std::string(field), TagValue(PositionInSet(0, *totalIter)))));
}
}
/// \endcond
/*!
* \brief Converts TRACKTOTAL/DISCTOTAL/PARTTOTAL to be included in the TRACKNUMBER/DISCNUMBER/PARTNUMBER fields instead.
*/
void VorbisComment::convertTotalFields(const std::string &diagContext, Diagnostics &diag)
{
extendPositionInSetField(VorbisCommentIds::trackNumber(), VorbisCommentIds::trackTotal(), diagContext, diag);
extendPositionInSetField(VorbisCommentIds::diskNumber(), VorbisCommentIds::diskTotal(), diagContext, diag);
extendPositionInSetField(VorbisCommentIds::partNumber(), VorbisCommentIds::partTotal(), diagContext, diag);
}
/*! /*!
* \brief Internal implementation for parsing. * \brief Internal implementation for parsing.
*/ */
template <class StreamType> template <class StreamType> void VorbisComment::internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag)
void VorbisComment::internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, std::uint64_t &padding, Diagnostics &diag)
{ {
// prepare parsing // prepare parsing
static const string context("parsing Vorbis comment"); static const string context("parsing Vorbis comment");
@ -235,7 +167,7 @@ void VorbisComment::internalParse(StreamType &stream, std::uint64_t maxSize, Vor
if (!skipSignature) { if (!skipSignature) {
CHECK_MAX_SIZE(7) CHECK_MAX_SIZE(7)
stream.read(sig, 7); stream.read(sig, 7);
skipSignature = (BE::toInt<std::uint64_t>(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u; skipSignature = (BE::toUInt64(sig) & 0xffffffffffffff00u) == 0x03766F7262697300u;
} }
if (skipSignature) { if (skipSignature) {
// read vendor (length prefixed string) // read vendor (length prefixed string)
@ -308,18 +240,13 @@ void VorbisComment::internalParse(StreamType &stream, std::uint64_t maxSize, Vor
} }
} }
if (bytesRemaining) { if (bytesRemaining) {
diag.emplace_back(DiagLevel::Information, argsToString(bytesRemaining, " bytes left in last segment."), context); diag.emplace_back(DiagLevel::Warning, argsToString(bytesRemaining, " bytes left in last segment."), context);
padding += bytesRemaining;
} }
} }
if (flags & VorbisCommentFlags::ConvertTotalFields) {
convertTotalFields(context, diag);
}
} }
/*! /*!
* \brief Parses tag information using the specified Ogg \a iterator. * \brief Parses tag information using the specified OGG \a iterator.
* *
* \throws Throws std::ios_base::failure when an IO error occurs. * \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws TagParser::Failure or a derived exception when a parsing * \throws Throws TagParser::Failure or a derived exception when a parsing
@ -327,24 +254,11 @@ void VorbisComment::internalParse(StreamType &stream, std::uint64_t maxSize, Vor
*/ */
void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, Diagnostics &diag) void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, Diagnostics &diag)
{ {
auto padding = std::uint64_t(); internalParse(iterator, iterator.streamSize(), flags, diag);
internalParse(iterator, iterator.streamSize(), flags, padding, diag);
} }
/*! /*!
* \brief Parses tag information using the specified Ogg \a iterator. * \brief Parses tag information using the specified OGG \a iterator.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws TagParser::Failure or a derived exception when a parsing
* error occurs.
*/
void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, std::uint64_t &padding, Diagnostics &diag)
{
internalParse(iterator, iterator.streamSize(), flags, padding, diag);
}
/*!
* \brief Parses tag information using the specified Ogg \a iterator.
* *
* \throws Throws std::ios_base::failure when an IO error occurs. * \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws TagParser::Failure or a derived exception when a parsing * \throws Throws TagParser::Failure or a derived exception when a parsing
@ -352,26 +266,9 @@ void VorbisComment::parse(OggIterator &iterator, VorbisCommentFlags flags, std::
*/ */
void VorbisComment::parse(istream &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag) void VorbisComment::parse(istream &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag)
{ {
auto padding = std::uint64_t(); internalParse(stream, maxSize, flags, diag);
internalParse(stream, maxSize, flags, padding, diag);
} }
/// \cond
static std::uint32_t makeField(VorbisCommentField &field, BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
{
if (field.value().isEmpty()) {
return 0;
}
try {
if (field.make(writer, flags, diag)) {
return 1;
}
} catch (const Failure &) {
}
return 0;
}
/// \endcond
/*! /*!
* \brief Writes tag information to the specified \a stream. * \brief Writes tag information to the specified \a stream.
* *
@ -403,14 +300,16 @@ void VorbisComment::make(std::ostream &stream, VorbisCommentFlags flags, Diagnos
writer.writeUInt32LE(0); writer.writeUInt32LE(0);
// write fields // write fields
std::uint32_t fieldsWritten = 0; std::uint32_t fieldsWritten = 0;
static const auto coverId = std::string(VorbisCommentIds::cover());
for (auto &i : fields()) { for (auto &i : fields()) {
if (i.first != coverId) { // write cover at the end VorbisCommentField &field = i.second;
fieldsWritten += makeField(i.second, writer, flags, diag); if (!field.value().isEmpty()) {
try {
if (field.make(writer, flags, diag)) {
++fieldsWritten;
}
} catch (const Failure &) {
} }
} }
if (const auto cover = fields().find(coverId); cover != fields().end()) {
fieldsWritten += makeField(cover->second, writer, flags, diag);
} }
// write field count // write field count
const auto framingByteOffset = stream.tellp(); const auto framingByteOffset = stream.tellp();

View File

@ -7,8 +7,6 @@
#include "../fieldbasedtag.h" #include "../fieldbasedtag.h"
#include "../mediaformat.h" #include "../mediaformat.h"
class OverallTests;
namespace TagParser { namespace TagParser {
class OggIterator; class OggIterator;
@ -26,7 +24,6 @@ public:
class TAG_PARSER_EXPORT VorbisComment : public FieldMapBasedTag<VorbisComment> { class TAG_PARSER_EXPORT VorbisComment : public FieldMapBasedTag<VorbisComment> {
friend class FieldMapBasedTag<VorbisComment>; friend class FieldMapBasedTag<VorbisComment>;
friend class ::OverallTests;
public: public:
VorbisComment(); VorbisComment();
@ -42,7 +39,6 @@ public:
bool setValue(KnownField field, const TagValue &value) override; bool setValue(KnownField field, const TagValue &value) override;
void parse(OggIterator &iterator, VorbisCommentFlags flags, Diagnostics &diag); void parse(OggIterator &iterator, VorbisCommentFlags flags, Diagnostics &diag);
void parse(OggIterator &iterator, VorbisCommentFlags flags, std::uint64_t &padding, Diagnostics &diag);
void parse(std::istream &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag); void parse(std::istream &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag);
void make(std::ostream &stream, VorbisCommentFlags flags, Diagnostics &diag); void make(std::ostream &stream, VorbisCommentFlags flags, Diagnostics &diag);
@ -55,10 +51,7 @@ protected:
KnownField internallyGetKnownField(const IdentifierType &id) const; KnownField internallyGetKnownField(const IdentifierType &id) const;
private: private:
template <class StreamType> template <class StreamType> void internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag);
void internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, std::uint64_t &padding, Diagnostics &diag);
void extendPositionInSetField(std::string_view field, std::string_view totalField, const std::string &diagContext, Diagnostics &diag);
void convertTotalFields(const std::string &diagContext, Diagnostics &diag);
private: private:
TagValue m_vendor; TagValue m_vendor;

View File

@ -205,7 +205,7 @@ bool VorbisCommentField::make(BinaryWriter &writer, VorbisCommentFlags flags, Di
bufferStream.rdbuf()->pubsetbuf(buffer.get(), requiredSize); bufferStream.rdbuf()->pubsetbuf(buffer.get(), requiredSize);
#endif #endif
pictureBlock.make(bufferStream); pictureBlock.make(bufferStream);
#if !(defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)) #if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
bufferStream.read(buffer.get(), static_cast<std::streamsize>(requiredSize)); bufferStream.read(buffer.get(), static_cast<std::streamsize>(requiredSize));
#endif #endif
valueString = encodeBase64(reinterpret_cast<std::uint8_t *>(buffer.get()), requiredSize); valueString = encodeBase64(reinterpret_cast<std::uint8_t *>(buffer.get()), requiredSize);

View File

@ -19,8 +19,7 @@ enum class VorbisCommentFlags : std::uint8_t {
None = 0x0, /**< Regular parsing/making. */ None = 0x0, /**< Regular parsing/making. */
NoSignature = 0x1, /**< Skips the signature when parsing and making. */ NoSignature = 0x1, /**< Skips the signature when parsing and making. */
NoFramingByte = 0x2, /**< Doesn't expect the framing bit to be present when parsing; does not make the framing bit when making. */ NoFramingByte = 0x2, /**< Doesn't expect the framing bit to be present when parsing; does not make the framing bit when making. */
NoCovers = 0x4, /**< Skips all covers when making. */ NoCovers = 0x4 /**< Skips all covers when making. */
ConvertTotalFields = 0x8, /**< Converts TRACKTOTAL/DISCTOTAL/PARTTOTAL to be included in the TRACKNUMBER/DISCNUMBER/PARTNUMBER fields. */
}; };
} // namespace TagParser } // namespace TagParser

View File

@ -9,12 +9,7 @@ namespace TagParser {
/*! /*!
* \brief Encapsulates Vorbis comment field names. * \brief Encapsulates Vorbis comment field names.
* \sa * \sa See https://xiph.org/vorbis/doc/v-comment.html for the upstream documentation of the field names.
* - See https://xiph.org/vorbis/doc/v-comment.html for the upstream documentation of the field names.
* - See https://wiki.xiph.org/Field_names for an additional proposal that is most notably introducing
* `DISCNUMBER` and `TOTAL` fields.
* - See https://wiki.hydrogenaud.io/index.php?title=Tag_Mapping for further conventions and a
* comparision with other formats.
*/ */
namespace VorbisCommentIds { namespace VorbisCommentIds {
@ -22,18 +17,10 @@ constexpr TAG_PARSER_EXPORT std::string_view trackNumber()
{ {
return "TRACKNUMBER"; return "TRACKNUMBER";
} }
constexpr TAG_PARSER_EXPORT std::string_view trackTotal()
{
return "TRACKTOTAL";
}
constexpr TAG_PARSER_EXPORT std::string_view diskNumber() constexpr TAG_PARSER_EXPORT std::string_view diskNumber()
{ {
return "DISCNUMBER"; return "DISCNUMBER";
} }
constexpr TAG_PARSER_EXPORT std::string_view diskTotal()
{
return "DISCTOTAL";
}
constexpr TAG_PARSER_EXPORT std::string_view part() constexpr TAG_PARSER_EXPORT std::string_view part()
{ {
return "PART"; return "PART";
@ -42,10 +29,6 @@ constexpr TAG_PARSER_EXPORT std::string_view partNumber()
{ {
return "PARTNUMBER"; return "PARTNUMBER";
} }
constexpr TAG_PARSER_EXPORT std::string_view partTotal()
{
return "PARTTOTAL";
}
constexpr TAG_PARSER_EXPORT std::string_view title() constexpr TAG_PARSER_EXPORT std::string_view title()
{ {
return "TITLE"; return "TITLE";
@ -206,10 +189,6 @@ constexpr TAG_PARSER_EXPORT std::string_view bpm()
{ {
return "BPM"; return "BPM";
} }
constexpr TAG_PARSER_EXPORT std::string_view publisherWebpage()
{
return "WWWPUBLISHER";
}
} // namespace VorbisCommentIds } // namespace VorbisCommentIds

View File

@ -25,7 +25,7 @@ void VorbisIdentificationHeader::parseHeader(OggIterator &iterator)
{ {
char buff[30 - 7]; char buff[30 - 7];
iterator.read(buff, 7); iterator.read(buff, 7);
if ((BE::toInt<std::uint64_t>(buff) & 0xffffffffffffff00u) != 0x01766F7262697300u) { if ((BE::toUInt64(buff) & 0xffffffffffffff00u) != 0x01766F7262697300u) {
throw InvalidDataException(); // not Vorbis identification header throw InvalidDataException(); // not Vorbis identification header
} }
iterator.read(buff, sizeof(buff)); iterator.read(buff, sizeof(buff));

View File

@ -186,8 +186,7 @@ void WaveAudioStream::internalParseHeader(Diagnostics &diag, AbortableProgressFe
MpegAudioFrame frame; MpegAudioFrame frame;
frame.parseHeader(m_reader, diag); frame.parseHeader(m_reader, diag);
MpegAudioFrameStream::addInfo(frame, *this); MpegAudioFrameStream::addInfo(frame, *this);
m_bitrate = frame.isXingFramefieldPresent() m_bitrate = frame.isXingFramefieldPresent() ? ((static_cast<double>(m_size) * 8.0)
? ((static_cast<double>(m_size) * 8.0)
/ (static_cast<double>(frame.xingFrameCount() * frame.sampleCount()) / static_cast<double>(frame.samplingFrequency())) / 1024.0) / (static_cast<double>(frame.xingFrameCount() * frame.sampleCount()) / static_cast<double>(frame.samplingFrequency())) / 1024.0)
: frame.bitrate(); : frame.bitrate();
m_bytesPerSecond = static_cast<std::uint32_t>(m_bitrate * 125); m_bytesPerSecond = static_cast<std::uint32_t>(m_bitrate * 125);