Add tests for FLAC files

This commit is contained in:
Martchus 2016-05-21 22:11:08 +02:00
parent a84ac37dbe
commit 53f0903c3b
7 changed files with 232 additions and 20 deletions

View File

@ -43,7 +43,7 @@ download() {
skipping
fi
popd
# extraction
mkdir "$destdir"
pushd "$destdir"
@ -59,12 +59,12 @@ download() {
popd
else
skipping
fi
fi
}
download_rsync() {
title="$1" cmd="$2"
inform "Downloading \"$title\" ..."
if [[ ! -d $destdir ]]; then
# actual download
@ -75,7 +75,7 @@ download_rsync() {
fi
else
skipping
fi
fi
}
# enter testfiles directory
@ -95,3 +95,8 @@ download \
download_rsync \
'MTX Test Data' \
'rsync -rv --links --delete belgarath.bunkus.org::mtx-test-data mtx-test-data'
# convert FLAC files for FLAC tests with ffmpeg
mkdir -p flac
[[ ! -f flac/test.flac ]] && ffmpeg -i mtx-test-data/alac/othertest-itunes.m4a -c:a flac flac/test.flac # raw FLAC stream
[[ ! -f flac/test.ogg ]] && ffmpeg -i flac/test.flac -c:a copy flac/test.ogg # FLAC in Ogg

View File

@ -51,6 +51,20 @@ VorbisComment *FlacStream::createVorbisComment()
return m_vorbisComment.get();
}
/*!
* \brief Removes the assigned Vorbis comment if one is assigned; does nothing otherwise.
* \returns Returns whether there were a Vorbis comment assigned.
*/
bool FlacStream::removeVorbisComment()
{
if(m_vorbisComment) {
m_vorbisComment.reset();
return true;
} else {
return false;
}
}
void FlacStream::internalParseHeader()
{
static const string context("parsing raw FLAC header");

View File

@ -21,6 +21,7 @@ public:
TrackType type() const;
VorbisComment *vorbisComment() const;
VorbisComment *createVorbisComment();
bool removeVorbisComment();
uint32 paddingSize() const;
uint32 streamOffset() const;

View File

@ -512,16 +512,16 @@ void MediaFileInfo::parseEverything()
* \param requiredTargets Specifies the required targets. Targets are ignored if not supported by the container.
* \return Returns an indication whether appropriate tags could be created for the file.
* \remarks
* - The ID3 related arguments are only practiced when the file format is MP3 or when the file format
* is unknown and \a treatUnknownFilesAsMp3Files is true. These arguments are ignored when creating
* tags for other known file formats such as MP4.
* - Tags might be removed as well. For example the existing ID3v1 tag of an MP3 file will be removed
* if \a id3v1Usage is set to TagUsage::Never.
* - The method might do nothing if the file already has appropriate tags.
* - This is only a convenience method. The task can be done by manually using the methods createId3v1Tag(),
* createId3v2Tag(), removeId3v1Tag() ... as well.
* - Some tag information might be discarded. For example when an ID3v2 tag needs to be removed (\a id3v2usage is set to TagUsage::Never)
* and an ID3v1 tag will be created instead not all fields can be transfered.
* - The ID3 related arguments are only practiced when the file format is MP3 or when the file format
* is unknown and \a treatUnknownFilesAsMp3Files is true. These arguments are ignored when creating
* tags for other known file formats such as MP4.
* - Tags might be removed as well. For example the existing ID3v1 tag of an MP3 file will be removed
* if \a id3v1Usage is set to TagUsage::Never.
* - The method might do nothing if the file already has appropriate tags.
* - This is only a convenience method. The task can be done by manually using the methods createId3v1Tag(),
* createId3v2Tag(), removeId3v1Tag() ... as well.
* - Some tag information might be discarded. For example when an ID3v2 tag needs to be removed (\a id3v2usage is set to TagUsage::Never)
* and an ID3v1 tag will be created instead not all fields can be transfered.
*/
bool MediaFileInfo::createAppropriateTags(bool treatUnknownFilesAsMp3Files, TagUsage id3v1usage, TagUsage id3v2usage, bool mergeMultipleSuccessiveId3v2Tags, bool keepExistingId3v2version, uint32 id3v2version, const std::vector<TagTarget> &requiredTargets)
{
@ -911,7 +911,6 @@ bool MediaFileInfo::removeId3v2Tag(Id3v2Tag *tag)
* To apply the removal and other changings call the applyChanges() method.
*
* \returns Returns whether there where ID3v2 tags assigned which could be removed.
*
* \sa applyChanges()
*/
bool MediaFileInfo::removeAllId3v2Tags()
@ -934,10 +933,8 @@ bool MediaFileInfo::removeAllId3v2Tags()
* To apply the created tag and other changings call the applyChanges() method.
*
* \returns Returns the first ID3v2 tag of the current file.
*
* \remarks The MediaFileInfo keeps the ownership over the created tag. It will be
* destroyed when the MediaFileInfo is invalidated.
*
* \sa applyChanges()
*/
Id3v2Tag *MediaFileInfo::createId3v2Tag()
@ -988,6 +985,9 @@ void MediaFileInfo::removeAllTags()
if(m_container) {
m_container->removeAllTags();
}
if(m_singleTrack && m_containerFormat == ContainerFormat::Flac) {
static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
}
m_id3v1Tag.reset();
m_id3v2Tags.clear();
}
@ -1347,6 +1347,69 @@ bool MediaFileInfo::id3v2ToId3v1()
}
}
/*!
* \brief Creates a Vorbis comment for the current file.
*
* This method does nothing if the tags/tracks of the current file haven't been parsed using
* the parseTags() and parseTracks() methods.
*
* If the file has already a Vorbis comment no new tag will be created.
*
* To apply the created tag and other changings call the applyChanges() method.
*
* \returns Returns the Vorbis comment or nullptr if creation is not possible.
*
* \sa applyChanges()
*/
VorbisComment *MediaFileInfo::createVorbisComment()
{
switch(m_containerFormat) {
case ContainerFormat::Ogg:
if(m_container) {
return static_cast<OggContainer *>(m_container.get())->createTag(TagTarget());
}
break;
case ContainerFormat::Flac:
if(m_singleTrack) {
return static_cast<FlacStream *>(m_singleTrack.get())->createVorbisComment();
}
break;
default:
;
}
return nullptr;
}
/*!
* \brief Removes all assigned Vorbis comment from the current file.
*
* To apply the removal and other changings call the applyChanges() method.
*
* \returns Returns whether there was an Vorbis comment assigned which could be removed.
*
* \sa applyChanges()
*/
bool MediaFileInfo::removeVorbisComment()
{
switch(m_containerFormat) {
case ContainerFormat::Ogg:
if(m_container) {
bool hadTags = static_cast<OggContainer *>(m_container.get())->tagCount();
static_cast<OggContainer *>(m_container.get())->removeAllTags();
return hadTags;
}
break;
case ContainerFormat::Flac:
if(m_singleTrack) {
return static_cast<FlacStream *>(m_singleTrack.get())->removeVorbisComment();
}
break;
default:
;
}
return false;
}
/*!
* \brief Stores all tags assigned to the current file in the specified vector.
*

View File

@ -131,6 +131,8 @@ public:
void mergeId3v2Tags();
bool id3v1ToId3v2();
bool id3v2ToId3v1();
VorbisComment *createVorbisComment();
bool removeVorbisComment();
// methods to get/wipe notifications
bool haveRelatedObjectsNotifications() const;

View File

@ -419,7 +419,9 @@ void OggContainer::internalMakeFile()
// check whether there is a new comment to be inserted into the current page
if(m_iterator.currentPageIndex() == currentParams->lastPageIndex && currentParams->firstSegmentIndex == static_cast<size_t>(-1)) {
makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams);
if(!currentParams->removed) {
makeVorbisCommentSegment(buffer, copyHelper, newSegmentSizes, currentComment, currentParams);
}
// proceed with next comment
if(++tagIterator != tagEnd) {
currentParams = &(currentComment = tagIterator->get())->oggParams();

View File

@ -45,6 +45,8 @@ class OverallTests : public TestFixture
CPPUNIT_TEST(testMp3Making);
CPPUNIT_TEST(testOggParsing);
CPPUNIT_TEST(testOggMaking);
CPPUNIT_TEST(testFlacParsing);
CPPUNIT_TEST(testFlacMaking);
CPPUNIT_TEST(testMkvParsing);
CPPUNIT_TEST(testMkvMaking);
CPPUNIT_TEST_SUITE_END();
@ -83,6 +85,9 @@ public:
void checkOggTestfile2();
void checkOggTestMetaData();
void checkFlacTestfile1();
void checkFlacTestfile2();
void setMkvTestMetaData();
void setMp4TestMetaData();
void setMp3TestMetaData();
@ -97,6 +102,8 @@ public:
void testMp3Making();
void testOggParsing();
void testOggMaking();
void testFlacParsing();
void testFlacMaking();
private:
MediaFileInfo m_fileInfo;
@ -967,6 +974,7 @@ void OverallTests::checkOggTestfile1()
switch(m_tagStatus) {
case TagStatus::Original:
CPPUNIT_ASSERT(tags.size() == 1);
CPPUNIT_ASSERT(tags.front()->value(KnownField::Encoder).toString() == "ffmpeg2theora 0.13");
// Theora tags are currently not supported and hence only the Vorbis comment is
// taken into account here
break;
@ -1003,6 +1011,7 @@ void OverallTests::checkOggTestfile2()
switch(m_tagStatus) {
case TagStatus::Original:
CPPUNIT_ASSERT(tags.size() == 1);
CPPUNIT_ASSERT(tags.front()->value(KnownField::Encoder).toString() == "opusenc from opus-tools 0.1.6");
break;
case TagStatus::TestMetaDataPresent:
checkOggTestMetaData();
@ -1034,6 +1043,76 @@ void OverallTests::checkOggTestMetaData()
m_preservedMetaData.pop();
}
/*!
* \brief Checks "flac/test.flac" (converted from "mtx-test-data/alac/othertest-itunes.m4a" via ffmpeg).
* \remarks Raw FLAC stream.
*/
void OverallTests::checkFlacTestfile1()
{
CPPUNIT_ASSERT(m_fileInfo.containerFormat() == ContainerFormat::Flac);
const auto tracks = m_fileInfo.tracks();
CPPUNIT_ASSERT(tracks.size() == 1);
for(const auto &track : tracks) {
CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Flac);
CPPUNIT_ASSERT(track->channelCount() == 2);
CPPUNIT_ASSERT(track->samplingFrequency() == 44100);
CPPUNIT_ASSERT(track->bitsPerSample() == 16);
}
const auto tags = m_fileInfo.tags();
switch(m_tagStatus) {
case TagStatus::Original:
// ffmpeg is able to set some tags from the original file (mtx-test-data/alac/othertest-itunes.m4a)
CPPUNIT_ASSERT(tags.size() == 1);
CPPUNIT_ASSERT(tags.front()->value(KnownField::Title).toString() == "Sad Song");
CPPUNIT_ASSERT(tags.front()->value(KnownField::Artist).toString() == "Oasis");
CPPUNIT_ASSERT(tags.front()->value(KnownField::Album).toString() == "Don't Go Away (Apple Lossless)");
CPPUNIT_ASSERT(tags.front()->value(KnownField::Genre).toString() == "Alternative & Punk");
CPPUNIT_ASSERT(tags.front()->value(KnownField::Encoder).toString() == "Lavf57.25.100");
CPPUNIT_ASSERT(tags.front()->value(KnownField::Year).toString() == "1998");
CPPUNIT_ASSERT(tags.front()->value(KnownField::Comment).isEmpty());
//CPPUNIT_ASSERT(tags.front()->value(KnownField::Cover).dataSize() == 0x58f3);
//CPPUNIT_ASSERT(BE::toUInt64(tags.front()->value(KnownField::Cover).dataPointer()) == 0xFFD8FFE000104A46);
CPPUNIT_ASSERT(tags.front()->value(KnownField::TrackPosition).toPositionInSet() == PositionInSet(3, 4));
CPPUNIT_ASSERT(tags.front()->value(KnownField::DiskPosition).toPositionInSet() == PositionInSet(1, 1));
break;
case TagStatus::TestMetaDataPresent:
checkOggTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
}
}
/*!
* \brief Checks "flac/test.ogg" (converted from "flac/test.flac" via ffmpeg).
* \remarks FLAC in Ogg.
*/
void OverallTests::checkFlacTestfile2()
{
CPPUNIT_ASSERT(m_fileInfo.containerFormat() == ContainerFormat::Ogg);
const auto tracks = m_fileInfo.tracks();
CPPUNIT_ASSERT(tracks.size() == 1);
for(const auto &track : tracks) {
CPPUNIT_ASSERT(track->mediaType() == MediaType::Audio);
CPPUNIT_ASSERT(track->format() == GeneralMediaFormat::Flac);
CPPUNIT_ASSERT(track->channelCount() == 2);
CPPUNIT_ASSERT(track->samplingFrequency() == 44100);
CPPUNIT_ASSERT(track->bitsPerSample() == 16);
}
const auto tags = m_fileInfo.tags();
switch(m_tagStatus) {
case TagStatus::Original:
CPPUNIT_ASSERT(tags.size() == 1);
break;
case TagStatus::TestMetaDataPresent:
checkOggTestMetaData();
break;
case TagStatus::Removed:
CPPUNIT_ASSERT(tags.size() == 0);
}
}
/*!
* \brief Creates a tag targeting the first track with some test meta data.
*/
@ -1115,7 +1194,7 @@ void OverallTests::setMp3TestMetaData()
void OverallTests::setOggTestMetaData()
{
// ensure a tag exists
Tag *tag = m_fileInfo.container()->createTag();
VorbisComment *tag = m_fileInfo.createVorbisComment();
// assign test meta data
tag->setValue(KnownField::Title, m_testTitle);
@ -1373,6 +1452,7 @@ void OverallTests::testMp3Making()
/*!
* \brief Tests the Ogg parser via MediaFileInfo.
* \remarks FLAC in Ogg is tested in testFlacParsing().
*/
void OverallTests::testOggParsing()
{
@ -1385,7 +1465,9 @@ void OverallTests::testOggParsing()
/*!
* \brief Tests the Ogg maker via MediaFileInfo.
* \remarks Relies on the parser to check results.
* \remarks
* - Relies on the parser to check results.
* - FLAC in Ogg is tested in testFlacMaking().
*/
void OverallTests::testOggMaking()
{
@ -1414,3 +1496,46 @@ void OverallTests::testOggMaking()
makeFile(TestUtilities::workingCopyPath("mtx-test-data/opus/v-opus.ogg"), modifyRoutine, &OverallTests::checkOggTestfile2);
}
}
/*!
* \brief Tests the FLAC parser via MediaFileInfo.
*/
void OverallTests::testFlacParsing()
{
cerr << endl << "FLAC parser" << endl;
m_fileInfo.setForceFullParse(false);
m_tagStatus = TagStatus::Original;
parseFile(TestUtilities::testFilePath("flac/test.flac"), &OverallTests::checkFlacTestfile1);
parseFile(TestUtilities::testFilePath("flac/test.ogg"), &OverallTests::checkFlacTestfile2);
}
/*!
* \brief Tests the FLAC maker via MediaFileInfo.
* \remarks Relies on the parser to check results.
*/
void OverallTests::testFlacMaking()
{
// full parse is required to determine padding
m_fileInfo.setForceFullParse(true);
// do the test under different conditions
for(m_mode = 0; m_mode != 0x2; ++m_mode) {
// TODO: setup test conditions
// print test conditions
list<string> testConditions;
if(m_mode & 0x1) {
testConditions.emplace_back("removing tag");
} else {
testConditions.emplace_back("modifying tag");
}
cerr << endl << "FLAC maker - testmode " << m_mode << ": " << joinStrings(testConditions, ", ") << endl;
// do actual tests
bool remove = m_mode & 0x1;
m_tagStatus = remove ? TagStatus::Removed : TagStatus::TestMetaDataPresent;
void (OverallTests::*modifyRoutine)(void) = remove ? &OverallTests::removeAllTags : &OverallTests::setOggTestMetaData;
makeFile(TestUtilities::workingCopyPath("flac/test.flac"), modifyRoutine, &OverallTests::checkFlacTestfile1);
makeFile(TestUtilities::workingCopyPath("flac/test.ogg"), modifyRoutine, &OverallTests::checkFlacTestfile2);
}
}