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
60 changed files with 318 additions and 740 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;
@ -192,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;
@ -209,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;
@ -237,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;
}; };
/*! /*!
@ -306,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.
*/ */
@ -476,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)
{ {
@ -716,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); m_duration = TimeSpan::fromSeconds(static_cast<double>(m_size) / (m_bytesPerSecond = static_cast<std::uint32_t>(m_bitrate * 125)));
} else {
m_bitrate = frame.bitrate();
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

@ -280,10 +280,6 @@ 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);
@ -291,16 +287,16 @@ void OggContainer::internalParseTags(Diagnostics &diag, AbortableProgressFeedbac
m_iterator.setFilter(m_iterator.currentPage().streamSerialNumber()); m_iterator.setFilter(m_iterator.currentPage().streamSerialNumber());
switch (params.streamFormat) { switch (params.streamFormat) {
case GeneralMediaFormat::Vorbis: case GeneralMediaFormat::Vorbis:
comment->parse(m_iterator, flags, 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, 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, diag); comment->parse(m_iterator, VorbisCommentFlags::NoSignature | VorbisCommentFlags::NoFramingByte, diag);
break; break;
default: default:
diag.emplace_back(DiagLevel::Critical, "Stream format not supported.", "parsing tags from OGG streams"); diag.emplace_back(DiagLevel::Critical, "Stream format not supported.", "parsing tags from OGG streams");
@ -439,11 +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;
auto copyHelper = CopyHelper<65307>(); std::uint64_t nextPageOffset;
auto updatedPageOffsets = std::vector<std::uint64_t>(); unordered_map<std::uint32_t, std::uint32_t> pageSequenceNumberBySerialNo;
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;

View File

@ -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,7 +48,6 @@ 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;
}; };
@ -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)
{ {
} }
@ -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,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;

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,8 +4,6 @@
#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>
@ -252,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,75 +146,12 @@ 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.
*/ */
@ -234,7 +167,7 @@ template <class StreamType> void VorbisComment::internalParse(StreamType &stream
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)
@ -310,10 +243,6 @@ template <class StreamType> void VorbisComment::internalParse(StreamType &stream
diag.emplace_back(DiagLevel::Warning, argsToString(bytesRemaining, " bytes left in last segment."), context); diag.emplace_back(DiagLevel::Warning, argsToString(bytesRemaining, " bytes left in last segment."), context);
} }
} }
if (flags & VorbisCommentFlags::ConvertTotalFields) {
convertTotalFields(context, diag);
}
} }
/*! /*!

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();
@ -55,8 +52,6 @@ protected:
private: private:
template <class StreamType> void internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, Diagnostics &diag); template <class StreamType> void internalParse(StreamType &stream, std::uint64_t maxSize, VorbisCommentFlags flags, 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

@ -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));