Allow setting track meta-data

Currently applying is only implemented for
MP4 tracks (only ID, name and language).
This commit is contained in:
Martchus 2017-06-06 23:26:20 +02:00
parent 4e59ed2742
commit b03273da44
8 changed files with 140 additions and 66 deletions

View File

@ -169,8 +169,8 @@ set(META_APP_AUTHOR "Martchus")
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_VERSION_MAJOR 6)
set(META_VERSION_MINOR 3)
set(META_VERSION_PATCH 1)
set(META_VERSION_MINOR 4)
set(META_VERSION_PATCH 0)
set(META_PUBLIC_SHARED_LIB_DEPENDS c++utilities)
set(META_PUBLIC_STATIC_LIB_DEPENDS c++utilities_static)
set(META_PRIVATE_COMPILE_DEFINITIONS LEGACY_API)

View File

@ -66,12 +66,14 @@ public:
uint64 id() const;
void setId(uint64 id);
const std::string name() const;
void setName(const std::string &name);
const ChronoUtilities::TimeSpan &duration() const;
double bitrate() const;
double maxBitrate() const;
const ChronoUtilities::DateTime &creationTime() const;
const ChronoUtilities::DateTime &modificationTime() const;
const std::string &language() const;
void setLanguage(const std::string &language);
uint32 samplingFrequency() const;
uint32 extensionSamplingFrequency() const;
uint16 bitsPerSample() const;
@ -86,6 +88,7 @@ public:
const Size &displaySize() const;
const Size &resolution() const;
const std::string &compressorName() const;
void setCompressorName(const std::string &compressorName);
uint16 depth() const;
uint32 fps() const;
const char *chromaFormat() const;
@ -93,8 +96,11 @@ public:
bool isInterlaced() const;
uint32 timeScale() const;
bool isEnabled() const;
void setEnabled(bool enabled);
bool isDefault() const;
void setDefault(bool isDefault);
bool isForced() const;
void setForced(bool forced);
bool hasLacing() const;
bool isEncrypted() const;
uint32 colorSpace() const;
@ -341,6 +347,15 @@ inline const std::string AbstractTrack::name() const
return m_name;
}
/*!
* \brief Sets the name.
* \remarks Whether the new value is applied when saving changes depends on the implementation.
*/
inline void AbstractTrack::setName(const std::string &name)
{
m_name = name;
}
/*!
* \brief Returns the duration if known; otherwise returns a TimeSpan of zero ticks.
*/
@ -391,6 +406,15 @@ inline const std::string &AbstractTrack::language() const
return m_language;
}
/*!
* \brief Sets the language of the track.
* \remarks Whether the new value is applied when saving changes depends on the implementation.
*/
inline void AbstractTrack::setLanguage(const std::string &language)
{
m_language = language;
}
/*!
* \brief Returns the number of samples per second if known; otherwise returns 0.
*/
@ -493,6 +517,15 @@ inline const std::string &AbstractTrack::compressorName() const
return m_compressorName;
}
/*!
* \brief Returns the compressor name if known; otherwise returns an empty string.
* \remarks Whether the new value is applied when saving changes depends on the implementation.
*/
inline void AbstractTrack::setCompressorName(const std::string &compressorName)
{
m_compressorName = compressorName;
}
/*!
* \brief Returns the bit depth if known; otherwise returns 0.
*/
@ -557,6 +590,15 @@ inline bool AbstractTrack::isEnabled() const
return m_enabled;
}
/*!
* \brief Sets whether the track is enabled.
* \remarks Whether the new value is applied when saving changes depends on the implementation.
*/
inline void AbstractTrack::setEnabled(bool enabled)
{
m_enabled = enabled;
}
/*!
* \brief Returns true if the track is denoted as default; otherwise returns false.
*/
@ -565,6 +607,15 @@ inline bool AbstractTrack::isDefault() const
return m_default;
}
/*!
* \brief Sets whether the track is a default track.
* \remarks Whether the new value is applied when saving changes depends on the implementation.
*/
inline void AbstractTrack::setDefault(bool isDefault)
{
m_default = isDefault;
}
/*!
* \brief Returns true if the track is denoted as forced; otherwise returns false.
*/
@ -573,6 +624,15 @@ inline bool AbstractTrack::isForced() const
return m_forced;
}
/*!
* \brief Sets whether the track is forced.
* \remarks Whether the new value is applied when saving changes depends on the implementation.
*/
inline void AbstractTrack::setForced(bool forced)
{
m_forced = forced;
}
/*!
* \brief Returns true if the track has lacing; otherwise returns false.
*/

View File

@ -106,7 +106,7 @@ private:
void removeAllTags();
void noop();
void createMkvWithNestedTags();
void addMp4Track();
void alterMp4Tracks();
void removeSecondTrack();
public:

View File

@ -41,7 +41,7 @@ void OverallTests::checkFlacTestfile1()
checkOggTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -71,7 +71,7 @@ void OverallTests::checkFlacTestfile2()
checkOggTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}

View File

@ -62,7 +62,7 @@ void OverallTests::checkMkvTestfile1()
checkMkvTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -102,7 +102,7 @@ void OverallTests::checkMkvTestfile2()
checkMkvTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -142,7 +142,7 @@ void OverallTests::checkMkvTestfile3()
checkMkvTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -176,7 +176,7 @@ void OverallTests::checkMkvTestfile4()
switch(m_tagStatus) {
case TagStatus::Original:
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
break;
case TagStatus::TestMetaDataPresent:
checkMkvTestMetaData();
@ -226,7 +226,7 @@ void OverallTests::checkMkvTestfile5()
checkMkvTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -267,7 +267,7 @@ void OverallTests::checkMkvTestfile6()
checkMkvTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -309,7 +309,7 @@ void OverallTests::checkMkvTestfile7()
checkMkvTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -351,7 +351,7 @@ void OverallTests::checkMkvTestfile8()
checkMkvTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -415,7 +415,7 @@ void OverallTests::checkMkvTestfileHandbrakeChapters()
checkMkvTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -451,7 +451,7 @@ void OverallTests::checkMkvTestfileNestedTags()
CPPUNIT_ASSERT(generalTagFound);
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tags.size());
}
}

View File

@ -77,7 +77,7 @@ void OverallTests::checkMp3Testfile1()
checkMp3TestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}

View File

@ -53,7 +53,7 @@ void OverallTests::checkMp4Testfile1()
checkMp4TestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -68,11 +68,11 @@ void OverallTests::checkMp4Testfile2()
for(const auto &track : tracks) {
switch(track->id()) {
case 1:
CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Avc);
CPPUNIT_ASSERT(track->format().sub == SubFormats::AvcHighProfile);
CPPUNIT_ASSERT_EQUAL(MediaType::Video, track->mediaType());
CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Avc, track->format().general);
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(SubFormats::AvcHighProfile), track->format().sub);
CPPUNIT_ASSERT_EQUAL(4.0, track->version());
CPPUNIT_ASSERT(track->creationTime().year() == 2013);
CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year());
CPPUNIT_ASSERT(track->pixelSize() == Size(1920, 750));
break;
case 2:
@ -87,21 +87,21 @@ void OverallTests::checkMp4Testfile2()
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(Mpeg4ChannelConfigs::FrontLeftFrontRight), track->channelConfig());
break;
case 3:
CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Ac3);
CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Ac3, track->format().general);
CPPUNIT_ASSERT_EQUAL("eng"s, track->language());
CPPUNIT_ASSERT(track->creationTime().year() == 2013);
CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year());
break;
case 4:
CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::DtsHd);
CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::DtsHd, track->format().general);
CPPUNIT_ASSERT_EQUAL("eng"s, track->language());
CPPUNIT_ASSERT(track->creationTime().year() == 2013);
CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year());
break;
case 6:
CPPUNIT_ASSERT(track->mediaType() == MediaType::Text);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::TimedText);
CPPUNIT_ASSERT(track->creationTime().year() == 2013);
CPPUNIT_ASSERT_EQUAL(MediaType::Text, track->mediaType());
CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::TimedText, track->format().general);
CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year());
break;
default:
CPPUNIT_FAIL("unknown track ID");
@ -147,13 +147,13 @@ void OverallTests::checkMp4Testfile3()
const auto tags = m_fileInfo.tags();
switch(m_tagStatus) {
case TagStatus::Original:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
break;
case TagStatus::TestMetaDataPresent:
checkMp4TestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -169,8 +169,8 @@ void OverallTests::checkMp4Testfile4()
for(const auto &track : tracks) {
switch(track->id()) {
case 1:
CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Alac);
CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Alac, track->format().general);
CPPUNIT_ASSERT(track->creationTime().year() == 2008);
CPPUNIT_ASSERT(track->channelCount() == 2);
CPPUNIT_ASSERT(track->bitsPerSample() == 16);
@ -199,7 +199,7 @@ void OverallTests::checkMp4Testfile4()
checkMp4TestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -215,9 +215,9 @@ void OverallTests::checkMp4Testfile5()
for(const auto &track : tracks) {
switch(track->id()) {
case 1:
CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Aac);
CPPUNIT_ASSERT(track->format().sub == SubFormats::AacMpeg4LowComplexityProfile);
CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Aac, track->format().general);
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(SubFormats::AacMpeg4LowComplexityProfile), track->format().sub);
CPPUNIT_ASSERT(track->format().extension & ExtensionFormats::SpectralBandReplication);
CPPUNIT_ASSERT(track->format().extension & ExtensionFormats::ParametricStereo);
CPPUNIT_ASSERT(track->creationTime().year() == 2014);
@ -235,13 +235,13 @@ void OverallTests::checkMp4Testfile5()
const auto tags = m_fileInfo.tags();
switch(m_tagStatus) {
case TagStatus::Original:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
break;
case TagStatus::TestMetaDataPresent:
checkMp4TestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -253,19 +253,19 @@ void OverallTests::checkMp4Testfile6()
CPPUNIT_ASSERT(m_fileInfo.containerFormat() == ContainerFormat::Mp4);
const auto tracks = m_fileInfo.tracks();
if(m_mode & Mp4TestFlags::RemoveTagOrTrack) {
CPPUNIT_ASSERT(tracks.size() == 4);
CPPUNIT_ASSERT_EQUAL(4_st, tracks.size());
} else {
CPPUNIT_ASSERT(tracks.size() == 6);
CPPUNIT_ASSERT_EQUAL(6_st, tracks.size());
}
bool track2Present = false, track5Present = false;
for(const auto &track : tracks) {
switch(track->id()) {
case 1:
CPPUNIT_ASSERT(track->mediaType() == MediaType::Video);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Avc);
CPPUNIT_ASSERT(track->format().sub == SubFormats::AvcHighProfile);
CPPUNIT_ASSERT(track->version() == 4);
CPPUNIT_ASSERT(track->creationTime().year() == 2013);
CPPUNIT_ASSERT_EQUAL(MediaType::Video, track->mediaType());
CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Avc, track->format().general);
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(SubFormats::AvcHighProfile), track->format().sub);
CPPUNIT_ASSERT_EQUAL(4.0, track->version());
CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year());
CPPUNIT_ASSERT(track->pixelSize() == Size(1920, 750));
break;
case 2:
@ -275,22 +275,23 @@ void OverallTests::checkMp4Testfile6()
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(SubFormats::AacMpeg4LowComplexityProfile), track->format().sub);
CPPUNIT_ASSERT(!(track->format().extension & ExtensionFormats::SpectralBandReplication));
CPPUNIT_ASSERT(!(track->format().extension & ExtensionFormats::ParametricStereo));
CPPUNIT_ASSERT_EQUAL("eng"s, track->language());
CPPUNIT_ASSERT_EQUAL("ger"s, track->language());
CPPUNIT_ASSERT_EQUAL("test"s, track->name());
CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year());
CPPUNIT_ASSERT_EQUAL(48000u, track->samplingFrequency());
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned char>(Mpeg4ChannelConfigs::FrontLeftFrontRight), track->channelConfig());
break;
case 3:
CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Ac3);
CPPUNIT_ASSERT(track->language() == "eng");
CPPUNIT_ASSERT(track->creationTime().year() == 2013);
CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::Ac3, track->format().general);
CPPUNIT_ASSERT_EQUAL("eng"s, track->language());
CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year());
break;
case 4:
CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::DtsHd);
CPPUNIT_ASSERT(track->language() == "eng");
CPPUNIT_ASSERT(track->creationTime().year() == 2013);
CPPUNIT_ASSERT_EQUAL(MediaType::Audio, track->mediaType());
CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::DtsHd, track->format().general);
CPPUNIT_ASSERT_EQUAL("eng"s, track->language());
CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year());
break;
case 5:
CPPUNIT_ASSERT(track5Present = !track5Present);
@ -299,11 +300,12 @@ void OverallTests::checkMp4Testfile6()
CPPUNIT_ASSERT_EQUAL(2012, track->creationTime().year());
CPPUNIT_ASSERT_EQUAL(44100u, track->samplingFrequency());
CPPUNIT_ASSERT_EQUAL(static_cast<byte>(Mpeg4ChannelConfigs::FrontLeftFrontRight), track->channelConfig());
CPPUNIT_ASSERT_EQUAL("new track"s, track->name());
break;
case 6:
CPPUNIT_ASSERT(track->mediaType() == MediaType::Text);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::TimedText);
CPPUNIT_ASSERT(track->creationTime().year() == 2013);
CPPUNIT_ASSERT_EQUAL(MediaType::Text, track->mediaType());
CPPUNIT_ASSERT_EQUAL(GeneralMediaFormat::TimedText, track->format().general);
CPPUNIT_ASSERT_EQUAL(2013, track->creationTime().year());
break;
default:
CPPUNIT_FAIL("unknown track ID");
@ -317,7 +319,7 @@ void OverallTests::checkMp4Testfile6()
CPPUNIT_ASSERT(track5Present);
}
CPPUNIT_ASSERT(m_fileInfo.tags().size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, m_fileInfo.tags().size());
}
/*!
@ -386,9 +388,13 @@ void OverallTests::setMp4TestMetaData()
}
/*!
* \brief Adds all tracks from mtx-test-data/mp4/10-DanseMacabreOp.40.m4a to the file to be tested.
* \brief Alters the tracks of the file to be testd.
*
* - Adds track from mtx-test-data/mp4/10-DanseMacabreOp.40.m4a
* - Sets the language of the 2nd track to German
* - Sets the name of the 2nd track to "test".
*/
void OverallTests::addMp4Track()
void OverallTests::alterMp4Tracks()
{
m_additionalFileInfo.setPath(TestUtilities::testFilePath("mtx-test-data/mp4/10-DanseMacabreOp.40.m4a"));
m_additionalFileInfo.reopen(true);
@ -401,7 +407,15 @@ void OverallTests::addMp4Track()
CPPUNIT_ASSERT_EQUAL(TrackType::Mp4Track, tracks[0]->type());
auto *track = static_cast<Mp4Track *>(tracks[0]);
CPPUNIT_ASSERT(static_cast<Mp4Container *>(m_additionalFileInfo.container())->removeTrack(track));
static_cast<Mp4Container *>(m_fileInfo.container())->addTrack(track);
CPPUNIT_ASSERT_EQUAL(0_st, m_additionalFileInfo.trackCount());
track->setName("new track");
auto *container = static_cast<Mp4Container *>(m_fileInfo.container());
CPPUNIT_ASSERT_EQUAL(5_st, container->trackCount());
container->addTrack(track);
CPPUNIT_ASSERT_EQUAL(6_st, container->trackCount());
auto &secondTrack = container->tracks()[1];
secondTrack->setLanguage("ger");
secondTrack->setName("test");
}
/*!
@ -482,7 +496,7 @@ void OverallTests::testMp4Making()
makeFile(TestUtilities::workingCopyPath("mtx-test-data/alac/othertest-itunes.m4a"), modifyRoutine, &OverallTests::checkMp4Testfile4);
makeFile(TestUtilities::workingCopyPath("mtx-test-data/aac/he-aacv2-ps.m4a"), modifyRoutine, &OverallTests::checkMp4Testfile5);
// -> add/remove tracks
modifyRoutine = (m_mode & RemoveTagOrTrack) ? &OverallTests::removeSecondTrack : &OverallTests::addMp4Track;
modifyRoutine = (m_mode & RemoveTagOrTrack) ? &OverallTests::removeSecondTrack : &OverallTests::alterMp4Tracks;
m_fileInfo.setTagPosition(ElementPosition::Keep);
makeFile(TestUtilities::workingCopyPath("mtx-test-data/mp4/1080p-DTS-HD-7.1.mp4"), modifyRoutine, &OverallTests::checkMp4Testfile6);
}

View File

@ -42,7 +42,7 @@ void OverallTests::checkOggTestfile1()
checkOggTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}
@ -77,7 +77,7 @@ void OverallTests::checkOggTestfile2()
checkOggTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
CPPUNIT_ASSERT_EQUAL(0_st, tracks.size());
}
}