UI: use codec info in database instead of rescanning files
This commit is contained in:
@@ -679,6 +679,14 @@ namespace lms::db
|
|||||||
return utils::fetchQuerySingleResult(session()->query<int>("SELECT COALESCE(AVG(t.bitrate), 0) FROM track t").where("release_id = ?").bind(getId()).where("bitrate > 0"));
|
return utils::fetchQuerySingleResult(session()->query<int>("SELECT COALESCE(AVG(t.bitrate), 0) FROM track t").where("release_id = ?").bind(getId()).where("bitrate > 0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<CodecType> Release::getCodecs() const
|
||||||
|
{
|
||||||
|
assert(session());
|
||||||
|
|
||||||
|
// Get the codec ordered by frequency
|
||||||
|
return utils::fetchQueryResults(session()->query<CodecType>("SELECT t.codec FROM track t").where("release_id = ?").bind(getId()).groupBy("t.codec").orderBy("COUNT(t.id) DESC"));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Artist::pointer> Release::getArtists(TrackArtistLinkType linkType) const
|
std::vector<Artist::pointer> Release::getArtists(TrackArtistLinkType linkType) const
|
||||||
{
|
{
|
||||||
assert(session());
|
assert(session());
|
||||||
|
|||||||
@@ -73,4 +73,95 @@ namespace lms::db
|
|||||||
return allowedAudioBitrates.find(bitrate) != std::cend(allowedAudioBitrates);
|
return allowedAudioBitrates.find(bitrate) != std::cend(allowedAudioBitrates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core::LiteralString containerTypeToString(ContainerType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case ContainerType::Unknown:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ContainerType::AIFF:
|
||||||
|
return "AIFF";
|
||||||
|
case ContainerType::APE:
|
||||||
|
return "APE";
|
||||||
|
case ContainerType::ASF:
|
||||||
|
return "ASF";
|
||||||
|
case ContainerType::DSF:
|
||||||
|
return "DSF";
|
||||||
|
case ContainerType::FLAC:
|
||||||
|
return "FLAC";
|
||||||
|
case ContainerType::MP4:
|
||||||
|
return "MP4";
|
||||||
|
case ContainerType::MPC:
|
||||||
|
return "MPC";
|
||||||
|
case ContainerType::MPEG:
|
||||||
|
return "MPEG";
|
||||||
|
case ContainerType::Ogg:
|
||||||
|
return "Ogg";
|
||||||
|
case ContainerType::Shorten:
|
||||||
|
return "Shorten";
|
||||||
|
case ContainerType::TrueAudio:
|
||||||
|
return "TrueAudio";
|
||||||
|
case ContainerType::WAV:
|
||||||
|
return "WAV";
|
||||||
|
case ContainerType::WavPack:
|
||||||
|
return "WavPack";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
core::LiteralString codecTypeToString(CodecType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case CodecType::Unknown:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CodecType::AAC:
|
||||||
|
return "AAC";
|
||||||
|
case CodecType::AC3:
|
||||||
|
return "AC3";
|
||||||
|
case CodecType::ALAC:
|
||||||
|
return "ALAC";
|
||||||
|
case CodecType::APE:
|
||||||
|
return "APE";
|
||||||
|
case CodecType::DSD:
|
||||||
|
return "DSD";
|
||||||
|
case CodecType::EAC3:
|
||||||
|
return "EAC3";
|
||||||
|
case CodecType::FLAC:
|
||||||
|
return "FLAC";
|
||||||
|
case CodecType::MP3:
|
||||||
|
return "MP3";
|
||||||
|
case CodecType::MP4ALS:
|
||||||
|
return "MP4ALS";
|
||||||
|
case CodecType::MPC7:
|
||||||
|
return "MPC7";
|
||||||
|
case CodecType::MPC8:
|
||||||
|
return "MPC8";
|
||||||
|
case CodecType::Opus:
|
||||||
|
return "Opus";
|
||||||
|
case CodecType::PCM:
|
||||||
|
return "PCM";
|
||||||
|
case CodecType::Shorten:
|
||||||
|
return "Shorten";
|
||||||
|
case CodecType::TrueAudio:
|
||||||
|
return "TrueAudio";
|
||||||
|
case CodecType::Vorbis:
|
||||||
|
return "Vorbis";
|
||||||
|
case CodecType::WavPack:
|
||||||
|
return "WavPack";
|
||||||
|
case CodecType::WMA1:
|
||||||
|
return "WMA1";
|
||||||
|
case CodecType::WMA2:
|
||||||
|
return "WMA2";
|
||||||
|
case CodecType::WMA9Pro:
|
||||||
|
return "WMA9Pro";
|
||||||
|
case CodecType::WMA9Lossless:
|
||||||
|
return "WMA9Lossless";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
} // namespace lms::db
|
} // namespace lms::db
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ namespace lms::db
|
|||||||
std::optional<std::string> getCopyright() const;
|
std::optional<std::string> getCopyright() const;
|
||||||
std::optional<std::string> getCopyrightURL() const;
|
std::optional<std::string> getCopyrightURL() const;
|
||||||
std::size_t getMeanBitrate() const;
|
std::size_t getMeanBitrate() const;
|
||||||
|
std::vector<CodecType> getCodecs() const;
|
||||||
|
|
||||||
// Accessors
|
// Accessors
|
||||||
std::string_view getName() const { return _name; }
|
std::string_view getName() const { return _name; }
|
||||||
|
|||||||
@@ -286,6 +286,8 @@ namespace lms::db
|
|||||||
WavPack = 13,
|
WavPack = 13,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
core::LiteralString containerTypeToString(ContainerType type);
|
||||||
|
|
||||||
enum class CodecType
|
enum class CodecType
|
||||||
{
|
{
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
@@ -312,4 +314,6 @@ namespace lms::db
|
|||||||
WMA9Pro = 20,
|
WMA9Pro = 20,
|
||||||
WMA9Lossless = 21,
|
WMA9Lossless = 21,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
core::LiteralString codecTypeToString(CodecType type);
|
||||||
} // namespace lms::db
|
} // namespace lms::db
|
||||||
|
|||||||
@@ -1214,6 +1214,40 @@ namespace lms::db::tests
|
|||||||
checkExpectedBitrate(192); // 0 should not be taken into account
|
checkExpectedBitrate(192); // 0 should not be taken into account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DatabaseFixture, Release_codec)
|
||||||
|
{
|
||||||
|
ScopedRelease release1{ session, "MyRelease1" };
|
||||||
|
ScopedTrack track1{ session };
|
||||||
|
ScopedTrack track2{ session };
|
||||||
|
ScopedTrack track3{ session };
|
||||||
|
ScopedTrack track4{ session };
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
ASSERT_EQ(release1->getCodecs().size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
track1.get().modify()->setCodec(CodecType::FLAC);
|
||||||
|
track1.get().modify()->setRelease(release1.get());
|
||||||
|
|
||||||
|
track2.get().modify()->setCodec(CodecType::MP3);
|
||||||
|
track2.get().modify()->setRelease(release1.get());
|
||||||
|
|
||||||
|
track3.get().modify()->setCodec(CodecType::MP3);
|
||||||
|
track3.get().modify()->setRelease(release1.get());
|
||||||
|
|
||||||
|
track4.get().modify()->setCodec(CodecType::AAC);
|
||||||
|
track4.get().modify()->setRelease(release1.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(release1->getCodecs(), (std::vector<CodecType>{ CodecType::MP3, CodecType::FLAC, CodecType::AAC }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Release_trackCount)
|
TEST_F(DatabaseFixture, Release_trackCount)
|
||||||
{
|
{
|
||||||
ScopedRelease release1{ session, "MyRelease1" };
|
ScopedRelease release1{ session, "MyRelease1" };
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ namespace lms::api::subsonic
|
|||||||
bool isOutputFormatCompatible(const std::filesystem::path& trackPath, audio::OutputFormat outputFormat)
|
bool isOutputFormatCompatible(const std::filesystem::path& trackPath, audio::OutputFormat outputFormat)
|
||||||
{
|
{
|
||||||
// TODO: put this information in db during scan
|
// TODO: put this information in db during scan
|
||||||
|
// It is in base only for tracks, not yet for podcasts
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) };
|
const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) };
|
||||||
|
|||||||
@@ -19,20 +19,17 @@
|
|||||||
|
|
||||||
#include "ReleaseView.hpp"
|
#include "ReleaseView.hpp"
|
||||||
|
|
||||||
#include <Wt/WContainerWidget.h>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include <Wt/WAnchor.h>
|
#include <Wt/WAnchor.h>
|
||||||
|
#include <Wt/WContainerWidget.h>
|
||||||
#include <Wt/WImage.h>
|
#include <Wt/WImage.h>
|
||||||
#include <Wt/WPushButton.h>
|
#include <Wt/WPushButton.h>
|
||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
|
|
||||||
#include "core/String.hpp"
|
#include "core/String.hpp"
|
||||||
|
|
||||||
#include "audio/AudioTypes.hpp"
|
|
||||||
#include "audio/Exception.hpp"
|
|
||||||
#include "audio/IAudioFileInfo.hpp"
|
|
||||||
#include "audio/IAudioFileInfoParser.hpp"
|
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
#include "database/objects/Artist.hpp"
|
#include "database/objects/Artist.hpp"
|
||||||
@@ -142,28 +139,20 @@ namespace lms::ui
|
|||||||
releaseInfo->bindString("release-labels", core::stringUtils::joinStrings(labels, " · "));
|
releaseInfo->bindString("release-labels", core::stringUtils::joinStrings(labels, " · "));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: save in DB and aggregate all this
|
// Codecs
|
||||||
for (const db::Track::pointer& track : db::Track::find(LmsApp->getDbSession(), db::Track::FindParameters{}.setRelease(releaseId).setRange(db::Range{ 0, 1 })).results)
|
|
||||||
{
|
{
|
||||||
try
|
std::string codecStr;
|
||||||
|
for (db::CodecType codec : release->getCodecs())
|
||||||
{
|
{
|
||||||
const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) };
|
if (!codecStr.empty())
|
||||||
|
codecStr += " · ";
|
||||||
audio::AudioFileInfoParseOptions parseOptions;
|
codecStr += db::codecTypeToString(codec).str();
|
||||||
parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Fast; // only coded needed
|
|
||||||
parseOptions.readImages = false;
|
|
||||||
parseOptions.readTags = false;
|
|
||||||
const auto audioFile{ parser->parse(track->getAbsoluteFilePath(), parseOptions) };
|
|
||||||
|
|
||||||
if (audioFile->getAudioProperties())
|
|
||||||
{
|
|
||||||
releaseInfo->setCondition("if-has-codec", true);
|
|
||||||
releaseInfo->bindString("codec", audio::codecTypeToString(audioFile->getAudioProperties()->codec).c_str(), Wt::TextFormat::Plain);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (const audio::Exception& e)
|
|
||||||
|
if (!codecStr.empty())
|
||||||
{
|
{
|
||||||
|
releaseInfo->setCondition("if-has-codec", true);
|
||||||
|
releaseInfo->bindString("codec", codecStr, Wt::TextFormat::Plain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,6 @@
|
|||||||
|
|
||||||
#include "core/Service.hpp"
|
#include "core/Service.hpp"
|
||||||
|
|
||||||
#include "audio/AudioTypes.hpp"
|
|
||||||
#include "audio/Exception.hpp"
|
|
||||||
#include "audio/IAudioFileInfo.hpp"
|
|
||||||
#include "audio/IAudioFileInfoParser.hpp"
|
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
#include "database/objects/Artist.hpp"
|
#include "database/objects/Artist.hpp"
|
||||||
@@ -131,25 +127,8 @@ namespace lms::ui::TrackListHelpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
trackInfo->setCondition("if-has-codec", true);
|
||||||
{
|
trackInfo->bindString("codec", db::codecTypeToString(track->getCodec()).c_str(), Wt::TextFormat::Plain);
|
||||||
const auto parser{ audio::createAudioFileInfoParser(audio::AudioFileInfoParserBackend::FFmpeg) };
|
|
||||||
|
|
||||||
audio::AudioFileInfoParseOptions parseOptions;
|
|
||||||
parseOptions.audioPropertiesReadStyle = audio::AudioFileInfoParseOptions::AudioPropertiesReadStyle::Fast; // only coded needed
|
|
||||||
parseOptions.readImages = false;
|
|
||||||
parseOptions.readTags = false;
|
|
||||||
const auto audioFile{ parser->parse(track->getAbsoluteFilePath(), parseOptions) };
|
|
||||||
|
|
||||||
if (audioFile->getAudioProperties())
|
|
||||||
{
|
|
||||||
trackInfo->setCondition("if-has-codec", true);
|
|
||||||
trackInfo->bindString("codec", audio::codecTypeToString(audioFile->getAudioProperties()->codec).c_str(), Wt::TextFormat::Plain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const audio::Exception& e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
trackInfo->bindString("duration", utils::durationToString(track->getDuration()));
|
trackInfo->bindString("duration", utils::durationToString(track->getDuration()));
|
||||||
if (track->getBitrate())
|
if (track->getBitrate())
|
||||||
|
|||||||
Reference in New Issue
Block a user