diff --git a/SUBSONIC.md b/SUBSONIC.md index de749e9e..1361440c 100644 --- a/SUBSONIC.md +++ b/SUBSONIC.md @@ -11,6 +11,7 @@ OpenSubsonic is an initiative to patch and extend the legacy Subsonic API. You'l ## Extra fields The following extra fields are implemented: * `Album` response: + * `mediaType` * `played` * `musicBrainzId` * `genres` @@ -22,6 +23,10 @@ The following extra fields are implemented: * `isCompilation` * `discTitles`: discs with no subtitle are omitted * `Child` response: + * `bitDepth` + * `samplingRate` + * `channelCount` + * `mediaType` * `played` * `musicBrainzId`: note this is actually the recording MBID when this response refers to a song * `genres` @@ -33,6 +38,7 @@ The following extra fields are implemented: * `moods` * `replayGain` * `Artist` response: + * `mediaType` * `musicBrainzId` * `sortName` * `roles` diff --git a/src/libs/av/impl/AudioFile.cpp b/src/libs/av/impl/AudioFile.cpp index 9184e6b6..d6f3e632 100644 --- a/src/libs/av/impl/AudioFile.cpp +++ b/src/libs/av/impl/AudioFile.cpp @@ -281,9 +281,12 @@ namespace lms::av res.emplace(); res->index = streamIndex; res->bitrate = static_cast(avstream->codecpar->bit_rate); + res->bitsPerSample = static_cast(avstream->codecpar->bits_per_coded_sample); + res->channelCount = static_cast(avstream->codecpar->ch_layout.nb_channels); res->codec = avcodecToDecodingCodec(avstream->codecpar->codec_id); res->codecName = ::avcodec_get_name(avstream->codecpar->codec_id); assert(!res->codecName.empty()); // doc says it is never NULL + res->sampleRate = static_cast(avstream->codecpar->sample_rate); return res; } diff --git a/src/libs/av/include/av/IAudioFile.hpp b/src/libs/av/include/av/IAudioFile.hpp index 3dc06edb..6d9915c9 100644 --- a/src/libs/av/include/av/IAudioFile.hpp +++ b/src/libs/av/include/av/IAudioFile.hpp @@ -75,6 +75,9 @@ namespace lms::av { size_t index{}; std::size_t bitrate{}; + std::size_t bitsPerSample{}; + std::size_t channelCount{}; + std::size_t sampleRate{}; DecodingCodec codec; std::string codecName; }; diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index 9cb0a616..127ca117 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -34,7 +34,7 @@ namespace lms::db { namespace { - static constexpr Version LMS_DATABASE_VERSION{ 55 }; + static constexpr Version LMS_DATABASE_VERSION{ 56 }; } VersionInfo::VersionInfo() @@ -441,6 +441,16 @@ SELECT session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); } + void migrateFromV55(Session& session) + { + // Add bitsPerSample, channelCount and sampleRate + session.getDboSession()->execute("ALTER TABLE track ADD bits_per_sample INTEGER NOT NULL DEFAULT(0)"); + session.getDboSession()->execute("ALTER TABLE track ADD channel_count INTEGER NOT NULL DEFAULT(0)"); + session.getDboSession()->execute("ALTER TABLE track ADD sample_rate INTEGER NOT NULL DEFAULT(0)"); + + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } void doDbMigration(Session& session) { @@ -474,6 +484,7 @@ SELECT {52, migrateFromV52}, {53, migrateFromV53}, {54, migrateFromV54}, + {55, migrateFromV55}, }; { diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/Track.hpp index cc1999de..84cde313 100644 --- a/src/libs/database/include/database/Track.hpp +++ b/src/libs/database/include/database/Track.hpp @@ -133,10 +133,13 @@ namespace lms::db void setAbsoluteFilePath(const std::filesystem::path& filePath); void setRelativeFilePath(const std::filesystem::path& filePath); void setFileSize(std::size_t fileSize) { _fileSize = fileSize; } - void setDuration(std::chrono::milliseconds duration) { _duration = duration; } - void setBitrate(std::size_t bitrate) { _bitrate = bitrate; } void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; } void setAddedTime(Wt::WDateTime time) { _fileAdded = time; } + void setBitrate(std::size_t bitrate) { _bitrate = bitrate; } + void setBitsPerSample(std::size_t bitsPerSample) { _bitsPerSample = bitsPerSample; } + void setDuration(std::chrono::milliseconds duration) { _duration = duration; } + void setChannelCount(std::size_t channelCount) { _channelCount = channelCount; } + void setSampleRate(std::size_t channelCount) { _sampleRate = channelCount; } void setDate(const Wt::WDate& date) { _date = date; } void setYear(std::optional year) { _year = year; } void setOriginalDate(const Wt::WDate& date) { _originalDate = date; } @@ -164,8 +167,11 @@ namespace lms::db const std::filesystem::path& getAbsoluteFilePath() const { return _absoluteFilePath; } const std::filesystem::path& getRelativeFilePath() const { return _relativeFilePath; } long long getFileSize() const { return _fileSize; } - std::chrono::milliseconds getDuration() const { return _duration; } std::size_t getBitrate() const { return _bitrate; } + std::size_t getBitsPerSample() const { return _bitsPerSample; } + std::size_t getChannelCount() const { return _channelCount; } + std::chrono::milliseconds getDuration() const { return _duration; } + std::size_t getSampleRate() const { return _sampleRate; } const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; } const Wt::WDate& getDate() const { return _date; } std::optional getYear() const { return _year; } @@ -203,6 +209,9 @@ namespace lms::db Wt::Dbo::field(a, _name, "name"); Wt::Dbo::field(a, _duration, "duration"); Wt::Dbo::field(a, _bitrate, "bitrate"); + Wt::Dbo::field(a, _bitsPerSample, "bits_per_sample"); + Wt::Dbo::field(a, _channelCount, "channel_count"); + Wt::Dbo::field(a, _sampleRate, "sample_rate"); Wt::Dbo::field(a, _date, "date"); Wt::Dbo::field(a, _year, "year"); Wt::Dbo::field(a, _originalDate, "original_date"); @@ -240,8 +249,11 @@ namespace lms::db std::optional _totalTrack{}; std::string _discSubtitle; std::string _name; - std::chrono::duration _duration{}; int _bitrate{}; // in bps + int _bitsPerSample{}; + int _channelCount{}; + std::chrono::duration _duration{}; + int _sampleRate{}; Wt::WDate _date; std::optional _year; Wt::WDate _originalDate; diff --git a/src/libs/database/test/Track.cpp b/src/libs/database/test/Track.cpp index 592e1f2a..ec3a6b77 100644 --- a/src/libs/database/test/Track.cpp +++ b/src/libs/database/test/Track.cpp @@ -342,4 +342,28 @@ namespace lms::db::tests EXPECT_EQ(track->getRelativeFilePath(), "foo/file.path"); } } + + + TEST_F(DatabaseFixture, Track_audioProperties) + { + ScopedTrack track{ session }; + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setBitrate(128000); + track.get().modify()->setBitsPerSample(16); + track.get().modify()->setDuration(std::chrono::minutes{ 3 }); + track.get().modify()->setChannelCount(2); + track.get().modify()->setSampleRate(44100); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(track->getBitrate(), 128000); + EXPECT_EQ(track->getBitsPerSample(), 16); + EXPECT_EQ(track->getDuration(), std::chrono::minutes{ 3 }); + EXPECT_EQ(track->getChannelCount(), 2); + EXPECT_EQ(track->getSampleRate(), 44100); + } + } } \ No newline at end of file diff --git a/src/libs/metadata/impl/AvFormatTagReader.cpp b/src/libs/metadata/impl/AvFormatTagReader.cpp index 8d487c94..1f4852e7 100644 --- a/src/libs/metadata/impl/AvFormatTagReader.cpp +++ b/src/libs/metadata/impl/AvFormatTagReader.cpp @@ -144,6 +144,17 @@ namespace lms::metadata try { const auto audioFile{ av::parseAudioFile(p) }; + + _audioProperties.duration = audioFile->getContainerInfo().duration; + + const auto bestAudioStream{ audioFile->getBestStreamInfo() }; + if (bestAudioStream) + { + _audioProperties.bitrate = bestAudioStream->bitrate; + _audioProperties.bitsPerSample = bestAudioStream->bitsPerSample; + _audioProperties.channelCount = bestAudioStream->channelCount; + _audioProperties.sampleRate = bestAudioStream->sampleRate; + } _containerInfo = audioFile->getContainerInfo(); _metaDataMap = audioFile->getMetaData(); diff --git a/src/libs/metadata/impl/AvFormatTagReader.hpp b/src/libs/metadata/impl/AvFormatTagReader.hpp index db7bddde..857de190 100644 --- a/src/libs/metadata/impl/AvFormatTagReader.hpp +++ b/src/libs/metadata/impl/AvFormatTagReader.hpp @@ -40,12 +40,9 @@ namespace lms::metadata void visitTagValues(std::string_view tag, TagValueVisitor visitor) const override; void visitPerformerTags(PerformerVisitor visitor) const override; bool hasEmbeddedCover() const override { return _hasEmbeddedCover; } + const AudioProperties& getAudioProperties() const override { return _audioProperties; } - std::chrono::milliseconds getDuration() const override { return _containerInfo.duration; } - std::size_t getBitrate() const override { return _containerInfo.bitrate; } - std::size_t getBitsPerSample() const override { return 0; } - std::size_t getSampleRate() const override { return 0; } - + AudioProperties _audioProperties; av::IAudioFile::MetadataMap _metaDataMap; av::ContainerInfo _containerInfo; bool _hasEmbeddedCover{}; diff --git a/src/libs/metadata/impl/ITagReader.hpp b/src/libs/metadata/impl/ITagReader.hpp index e426b691..ee80933a 100644 --- a/src/libs/metadata/impl/ITagReader.hpp +++ b/src/libs/metadata/impl/ITagReader.hpp @@ -153,10 +153,6 @@ namespace lms::metadata virtual void visitPerformerTags(PerformerVisitor visitor) const = 0; virtual bool hasEmbeddedCover() const = 0; - - virtual std::chrono::milliseconds getDuration() const = 0; - virtual std::size_t getBitrate() const = 0; - virtual std::size_t getBitsPerSample() const = 0; - virtual std::size_t getSampleRate() const = 0; + virtual const AudioProperties& getAudioProperties() const = 0; }; } // namespace lms::metadata diff --git a/src/libs/metadata/impl/Parser.cpp b/src/libs/metadata/impl/Parser.cpp index 6a0a30e3..bfdb38e4 100644 --- a/src/libs/metadata/impl/Parser.cpp +++ b/src/libs/metadata/impl/Parser.cpp @@ -235,18 +235,12 @@ namespace lms::metadata { auto track{ std::make_unique() }; - processAudioProperties(tagReader, *track); + track->audioProperties = tagReader.getAudioProperties(); processTags(tagReader, *track); return track; } - void Parser::processAudioProperties(const ITagReader& tagReader, Track& track) - { - track.duration = tagReader.getDuration(); - track.bitrate = tagReader.getBitrate(); - } - void Parser::processTags(const ITagReader& tagReader, Track& track) { track.hasCover = tagReader.hasEmbeddedCover(); diff --git a/src/libs/metadata/impl/Parser.hpp b/src/libs/metadata/impl/Parser.hpp index ffbd89c1..87bd8f21 100644 --- a/src/libs/metadata/impl/Parser.hpp +++ b/src/libs/metadata/impl/Parser.hpp @@ -37,7 +37,6 @@ namespace lms::metadata void setArtistTagDelimiters(std::span delimiters) override { _artistTagDelimiters.assign(std::cbegin(delimiters), std::cend(delimiters)); } void setDefaultTagDelimiters(std::span delimiters) override { _defaultTagDelimiters.assign(std::cbegin(delimiters), std::cend(delimiters)); } - void processAudioProperties(const ITagReader& reader, Track& track); void processTags(const ITagReader& reader, Track& track); std::optional getMedium(const ITagReader& tagReader); diff --git a/src/libs/metadata/impl/TagLibTagReader.cpp b/src/libs/metadata/impl/TagLibTagReader.cpp index 340d6a11..8799a353 100644 --- a/src/libs/metadata/impl/TagLibTagReader.cpp +++ b/src/libs/metadata/impl/TagLibTagReader.cpp @@ -21,6 +21,7 @@ #include +#include #include #include #include @@ -198,6 +199,8 @@ namespace lms::metadata throw ParsingFailedException{}; } + computeAudioProperties(); + _propertyMap = _file.file()->properties(); // Some tags may not be known by TagLib @@ -305,6 +308,28 @@ namespace lms::metadata } } + void TagLibTagReader::computeAudioProperties() + { + const TagLib::AudioProperties* properties{ _file.audioProperties() }; + + // Common properties + _audioProperties.bitrate = static_cast(properties->bitrate() * 1000); + _audioProperties.channelCount = static_cast(_file.audioProperties()->channels()); + _audioProperties.duration = std::chrono::milliseconds{ properties->lengthInMilliseconds() }; + _audioProperties.sampleRate = static_cast(properties->sampleRate()); + + if (const auto * apeProperties{ dynamic_cast(properties) }) + _audioProperties.bitsPerSample = apeProperties->bitsPerSample(); + if (const auto * asfProperties{ dynamic_cast(properties) }) + _audioProperties.bitsPerSample = asfProperties->bitsPerSample(); + else if (const auto * flacProperties{ dynamic_cast(properties) }) + _audioProperties.bitsPerSample = flacProperties->bitsPerSample(); + else if (const auto * mp4Properties{ dynamic_cast(properties) }) + _audioProperties.bitsPerSample = mp4Properties->bitsPerSample(); + else if (const auto * wavePackProperties{ dynamic_cast(properties) }) + _audioProperties.bitsPerSample = wavePackProperties->bitsPerSample(); + } + void TagLibTagReader::visitTagValues(TagType tag, TagValueVisitor visitor) const { auto itTagNames{ tagMapping.find(tag) }; @@ -362,24 +387,4 @@ namespace lms::metadata } } } - - std::chrono::milliseconds TagLibTagReader::getDuration() const - { - return std::chrono::milliseconds{ _file.audioProperties()->lengthInMilliseconds() }; - } - - std::size_t TagLibTagReader::getBitrate() const - { - return static_cast(_file.audioProperties()->bitrate() * 1000); - } - - std::size_t TagLibTagReader::getBitsPerSample() const - { - return 0; // TODO - } - - std::size_t TagLibTagReader::getSampleRate() const - { - return static_cast(_file.audioProperties()->sampleRate()); - } } // namespace lms::metadata diff --git a/src/libs/metadata/impl/TagLibTagReader.hpp b/src/libs/metadata/impl/TagLibTagReader.hpp index 6aaa3476..3ada79af 100644 --- a/src/libs/metadata/impl/TagLibTagReader.hpp +++ b/src/libs/metadata/impl/TagLibTagReader.hpp @@ -38,17 +38,16 @@ namespace lms::metadata TagLibTagReader(const TagLibTagReader&) = delete; TagLibTagReader& operator=(const TagLibTagReader&) = delete; + void computeAudioProperties(); void visitTagValues(TagType tag, TagValueVisitor visitor) const override; void visitTagValues(std::string_view tag, TagValueVisitor visitor) const override; void visitPerformerTags(PerformerVisitor visitor) const override; bool hasEmbeddedCover() const override { return _hasEmbeddedCover; } - std::chrono::milliseconds getDuration() const override; - std::size_t getBitrate() const override; - std::size_t getBitsPerSample() const override; - std::size_t getSampleRate() const override; + const AudioProperties& getAudioProperties() const override { return _audioProperties; } const TagLib::FileRef _file; + AudioProperties _audioProperties; TagLib::PropertyMap _propertyMap; // case-insensitive keys bool _hasEmbeddedCover{}; }; diff --git a/src/libs/metadata/include/metadata/IParser.hpp b/src/libs/metadata/include/metadata/IParser.hpp index 568a6287..821b99a9 100644 --- a/src/libs/metadata/include/metadata/IParser.hpp +++ b/src/libs/metadata/include/metadata/IParser.hpp @@ -83,8 +83,18 @@ namespace lms::metadata } }; + struct AudioProperties + { + std::size_t bitrate{}; + std::size_t bitsPerSample{}; + std::size_t channelCount{}; + std::chrono::milliseconds duration{}; + std::size_t sampleRate{}; + }; + struct Track { + AudioProperties audioProperties; std::optional mbid; std::optional recordingMBID; std::string title; @@ -96,8 +106,6 @@ namespace lms::metadata std::vector labels; std::vector languages; Tags userExtraTags; - std::chrono::milliseconds duration{}; - std::size_t bitrate{}; std::optional year{}; Wt::WDate date; std::optional originalYear{}; diff --git a/src/libs/metadata/test/Parser.cpp b/src/libs/metadata/test/Parser.cpp index 45ddd48e..3c6bf9db 100644 --- a/src/libs/metadata/test/Parser.cpp +++ b/src/libs/metadata/test/Parser.cpp @@ -88,6 +88,16 @@ namespace lms::metadata std::unique_ptr track{ parser.parse(testTags) }; + // Audio properties + { + const AudioProperties& audioProperties{ testTags.getAudioProperties() }; + EXPECT_EQ(track->audioProperties.bitrate, audioProperties.bitrate); + EXPECT_EQ(track->audioProperties.bitsPerSample, audioProperties.bitsPerSample); + EXPECT_EQ(track->audioProperties.channelCount, audioProperties.channelCount); + EXPECT_EQ(track->audioProperties.duration, audioProperties.duration); + EXPECT_EQ(track->audioProperties.sampleRate, audioProperties.sampleRate); + } + EXPECT_EQ(track->acoustID, core::UUID::fromString("e987a441-e134-4960-8019-274eddacc418")); EXPECT_EQ(track->artistDisplayName, "MyArtist1 & MyArtist2"); ASSERT_EQ(track->artists.size(), 2); @@ -97,7 +107,6 @@ namespace lms::metadata EXPECT_EQ(track->artists[1].name, "MyArtist2"); EXPECT_EQ(track->artists[1].sortName, "MyArtist2SortName"); EXPECT_EQ(track->artists[1].mbid, core::UUID::fromString("5e2cf87f-c8d7-4504-8a86-954dc0840229")); - EXPECT_EQ(track->bitrate, TestTagReader::trackBitrate); ASSERT_EQ(track->composerArtists.size(), 2); EXPECT_EQ(track->composerArtists[0].name, "MyComposer1"); EXPECT_EQ(track->composerArtists[0].sortName, "MyComposerSortOrder1"); @@ -112,7 +121,6 @@ namespace lms::metadata EXPECT_EQ(track->date.year(), 2020); EXPECT_EQ(track->date.month(), 3); EXPECT_EQ(track->date.day(), 4); - EXPECT_EQ(track->duration, TestTagReader::trackDuration); EXPECT_FALSE(track->hasCover); ASSERT_EQ(track->genres.size(), 2); EXPECT_EQ(track->genres[0], "Genre1"); diff --git a/src/libs/metadata/test/TestTagReader.hpp b/src/libs/metadata/test/TestTagReader.hpp index b7aca082..e7f968f4 100644 --- a/src/libs/metadata/test/TestTagReader.hpp +++ b/src/libs/metadata/test/TestTagReader.hpp @@ -28,10 +28,14 @@ namespace lms::metadata class TestTagReader : public ITagReader { public: - static constexpr std::chrono::milliseconds trackDuration{ 180 }; - static constexpr std::size_t trackBitrate{ 128000 }; - static constexpr std::size_t trackBitsPerSample{ 16 }; - static constexpr std::size_t trackSampleRate{ 44000 }; + static constexpr AudioProperties audioProperties + { + .bitrate = 128000, + .bitsPerSample = 16, + .channelCount = 2, + .duration = std::chrono::seconds{180}, + .sampleRate = 44000, + }; using Tags = std::unordered_map>; using Performers = std::unordered_map>; @@ -73,10 +77,7 @@ namespace lms::metadata bool hasEmbeddedCover() const override { return false; }; - std::chrono::milliseconds getDuration() const override { return trackDuration; } - std::size_t getBitrate() const override { return trackBitrate; } - std::size_t getBitsPerSample() const override { return trackBitsPerSample; } - std::size_t getSampleRate() const override { return trackSampleRate; } + const AudioProperties& getAudioProperties() const override { return audioProperties; } private: const Tags _tags; diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp index bdd950ad..cd2606a7 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp @@ -593,7 +593,7 @@ namespace lms::scanner } // We estimate this is an audio file if the duration is not null - if (trackMetadata.duration == std::chrono::milliseconds::zero()) + if (trackMetadata.audioProperties.duration == std::chrono::milliseconds::zero()) { LMS_LOG(DBUPDATER, DEBUG, "Skipped '" << file.string() << "' (duration is 0)"); @@ -631,6 +631,13 @@ namespace lms::scanner // Track related data assert(track); + // Audio properties + track.modify()->setBitrate(trackMetadata.audioProperties.bitrate); + track.modify()->setBitsPerSample(trackMetadata.audioProperties.bitsPerSample); + track.modify()->setChannelCount(trackMetadata.audioProperties.channelCount); + track.modify()->setDuration(trackMetadata.audioProperties.duration); + track.modify()->setSampleRate(trackMetadata.audioProperties.sampleRate); + track.modify()->setRelativeFilePath(fileInfo->relativePath); track.modify()->setFileSize(fileInfo->fileSize); track.modify()->setLastWriteTime(fileInfo->lastWriteTime); @@ -683,8 +690,6 @@ namespace lms::scanner track.modify()->setDiscSubtitle(trackMetadata.medium ? trackMetadata.medium->name : ""); track.modify()->setClusters(getOrCreateClusters(dbSession, trackMetadata)); track.modify()->setName(title); - track.modify()->setDuration(trackMetadata.duration); - track.modify()->setBitrate(trackMetadata.bitrate); track.modify()->setAddedTime(Wt::WDateTime::currentDateTime()); track.modify()->setTrackNumber(trackMetadata.position); track.modify()->setDiscNumber(trackMetadata.medium ? trackMetadata.medium->position : std::nullopt); diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 3b43b219..f7a8a1aa 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -139,6 +139,10 @@ namespace lms::api::subsonic if (!context.enableOpenSubsonic) return trackResponse; + trackResponse.setAttribute("bitDepth", track->getBitsPerSample()); + trackResponse.setAttribute("samplingRate", track->getSampleRate()); + trackResponse.setAttribute("channelCount", track->getChannelCount()); + trackResponse.setAttribute("mediaType", "song"); { diff --git a/src/tools/metadata/LmsMetadata.cpp b/src/tools/metadata/LmsMetadata.cpp index 6d6f41da..4a5a836e 100644 --- a/src/tools/metadata/LmsMetadata.cpp +++ b/src/tools/metadata/LmsMetadata.cpp @@ -32,6 +32,17 @@ namespace lms::metadata { + std::ostream& operator<<(std::ostream& os, const AudioProperties& audioProperties) + { + os << "\tBitrate: " << audioProperties.bitrate << " bps" << std::endl; + os << "\tBitsPerSample: " << audioProperties.bitsPerSample << std::endl; + os << "\tChannelCount: " << audioProperties.channelCount << std::endl; + os << "\tDuration: " << std::fixed << std::setprecision(2) << audioProperties.duration.count() / 1000. << "s" << std::endl; + os << "\tSampleRate: " << audioProperties.sampleRate << std::endl; + + return os; + } + std::ostream& operator<<(std::ostream& os, const Artist& artist) { os << artist.name; @@ -107,6 +118,8 @@ namespace lms::metadata std::cout << "Parsing time: " << std::fixed << std::setprecision(2) << std::chrono::duration_cast(end - start).count() / 1000. << "ms" << std::endl; + std::cout << "Audio properties:\n" << track->audioProperties << std::endl; + std::cout << "Parsed metadata:" << std::endl; if (!track->artistDisplayName.empty()) @@ -178,9 +191,6 @@ namespace lms::metadata } } - std::cout << "Duration: " << std::fixed << std::setprecision(2) << track->duration.count() / 1000. << "s" << std::endl; - std::cout << "Bitrate: " << track->bitrate << " bps" << std::endl; - if (track->position) std::cout << "Position: " << *track->position << std::endl;