From 02c8995273568fc420df0805ac3eb98f13ef503b Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Tue, 8 Dec 2020 19:08:27 +0100 Subject: [PATCH 1/3] Calculate bitrate for ALAC files without it (#961) If the "alac" atom of an MP4 file has a zero bitrate, it is calculated. --- taglib/mp4/mp4properties.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/taglib/mp4/mp4properties.cpp b/taglib/mp4/mp4properties.cpp index faa43c27..a577ac3e 100644 --- a/taglib/mp4/mp4properties.cpp +++ b/taglib/mp4/mp4properties.cpp @@ -31,6 +31,27 @@ using namespace TagLib; +namespace +{ + // Calculate the total bytes used by audio data, used to calculate the bitrate + long long calculateMdatLength(const MP4::AtomList &list) + { + long long totalLength = 0; + for(MP4::AtomList::ConstIterator it = list.begin(); it != list.end(); ++it) { + long length = (*it)->length; + if(length == 0) + return 0; // for safety, see checkValid() in mp4file.cpp + + if((*it)->name == "mdat") + totalLength += length; + + totalLength += calculateMdatLength((*it)->children); + } + + return totalLength; + } +} + class MP4::Properties::PropertiesPrivate { public: @@ -224,6 +245,13 @@ MP4::Properties::read(File *file, Atoms *atoms) d->channels = data.at(73); d->bitrate = static_cast(data.toUInt(80U) / 1000.0 + 0.5); d->sampleRate = data.toUInt(84U); + + if(d->bitrate == 0 && d->length > 0) { + // There are files which do not contain a nominal bitrate, e.g. those + // generated by refalac64.exe. Calculate the bitrate from the audio + // data size (mdat atoms) and the duration. + d->bitrate = (calculateMdatLength(atoms->atoms) * 8) / d->length; + } } } From 3d71ea1ad263e399952810a442522efc7f36ef62 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Mon, 21 Dec 2020 13:31:24 +0100 Subject: [PATCH 2/3] ALAC: Test properties of file without bitrate --- tests/test_mp4.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 9e52de2b..13c17b22 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -28,10 +28,12 @@ #include #include #include +#include #include #include #include #include +#include "plainfile.h" #include "utils.h" using namespace std; @@ -42,6 +44,7 @@ class TestMP4 : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestMP4); CPPUNIT_TEST(testPropertiesAAC); CPPUNIT_TEST(testPropertiesALAC); + CPPUNIT_TEST(testPropertiesALACWithoutBitrate); CPPUNIT_TEST(testPropertiesM4V); CPPUNIT_TEST(testFreeForm); CPPUNIT_TEST(testCheckValid); @@ -92,6 +95,28 @@ public: CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec()); } + void testPropertiesALACWithoutBitrate() + { + ByteVector alacData = PlainFile(TEST_FILE_PATH_C("empty_alac.m4a")).readAll(); + CPPUNIT_ASSERT_GREATER(474U, alacData.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("alac"), alacData.mid(446, 4)); + // Set the bitrate to zero + for (int offset = 470; offset < 474; ++offset) { + alacData[offset] = 0; + } + ByteVectorStream alacStream(alacData); + MP4::File f(&alacStream); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3705, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::ALAC, f.audioProperties()->codec()); + } + void testPropertiesM4V() { MP4::File f(TEST_FILE_PATH_C("blank_video.m4v")); From 563fbaf82aa363940394ea1f91ec1c758f2e0621 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Sun, 27 Dec 2020 10:17:34 +0100 Subject: [PATCH 3/3] Handle the case when MP4 file header has zero bitrate This incorporates [6ca536b5] (mp4 properties: handle the case when mp4 file header has zero bitrate) from PR #899 with a more accurate bitrate calculation and a unit test. --- taglib/mp4/mp4properties.cpp | 9 ++++++++- tests/test_mp4.cpp | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/taglib/mp4/mp4properties.cpp b/taglib/mp4/mp4properties.cpp index a577ac3e..6c6976fa 100644 --- a/taglib/mp4/mp4properties.cpp +++ b/taglib/mp4/mp4properties.cpp @@ -234,7 +234,14 @@ MP4::Properties::read(File *file, Atoms *atoms) pos += 3; } pos += 10; - d->bitrate = static_cast((data.toUInt(pos) + 500) / 1000.0 + 0.5); + const unsigned int bitrateValue = data.toUInt(pos); + if(bitrateValue != 0 || d->length <= 0) { + d->bitrate = static_cast((bitrateValue + 500) / 1000.0 + 0.5); + } + else { + d->bitrate = static_cast( + (calculateMdatLength(atoms->atoms) * 8) / d->length); + } } } } diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 13c17b22..ba38c0f0 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -43,6 +43,7 @@ class TestMP4 : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestMP4); CPPUNIT_TEST(testPropertiesAAC); + CPPUNIT_TEST(testPropertiesAACWithoutBitrate); CPPUNIT_TEST(testPropertiesALAC); CPPUNIT_TEST(testPropertiesALACWithoutBitrate); CPPUNIT_TEST(testPropertiesM4V); @@ -81,6 +82,28 @@ public: CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec()); } + void testPropertiesAACWithoutBitrate() + { + ByteVector aacData = PlainFile(TEST_FILE_PATH_C("has-tags.m4a")).readAll(); + CPPUNIT_ASSERT_GREATER(1960U, aacData.size()); + CPPUNIT_ASSERT_EQUAL(ByteVector("mp4a"), aacData.mid(1890, 4)); + // Set the bitrate to zero + for (int offset = 1956; offset < 1960; ++offset) { + aacData[offset] = 0; + } + ByteVectorStream aacStream(aacData); + MP4::File f(&aacStream); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3708, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + CPPUNIT_ASSERT_EQUAL(MP4::Properties::AAC, f.audioProperties()->codec()); + } + void testPropertiesALAC() { MP4::File f(TEST_FILE_PATH_C("empty_alac.m4a"));