WIP replaygain support

This commit is contained in:
emeric
2020-04-25 14:39:49 +02:00
parent 4749fd7548
commit f41b6c38a7
22 changed files with 480 additions and 216 deletions
+9 -1
View File
@@ -40,7 +40,7 @@
namespace Database {
#define LMS_DATABASE_VERSION 21
#define LMS_DATABASE_VERSION 22
using Version = std::size_t;
@@ -246,6 +246,14 @@ CREATE TABLE "user_backup" (
{
_session.execute("DROP TABLE subsonic_settings");
}
else if (version == 21)
{
_session.execute("ALTER TABLE track ADD track_replay_gain REAL");
_session.execute("ALTER TABLE track ADD release_replay_gain REAL");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion();
}
else
{
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
+25 -4
View File
@@ -38,6 +38,14 @@ _filePath( p.string() )
{
}
std::size_t
Track::getCount(Session& session)
{
session.checkSharedLocked();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM track");
}
std::vector<Track::pointer>
Track::getAll(Session& session, std::optional<std::size_t> limit)
{
@@ -107,13 +115,26 @@ Track::create(Session& session, const std::filesystem::path& p)
return res;
}
std::vector<std::filesystem::path>
Track::getAllPaths(Session& session)
std::vector<std::pair<IdType, std::filesystem::path>>
Track::getAllPaths(Session& session, std::optional<std::size_t> offset, std::optional<std::size_t> size)
{
using QueryResultType = std::tuple<IdType, std::string>;
session.checkSharedLocked();
Wt::Dbo::collection<std::string> res = session.getDboSession().query<std::string>("SELECT file_path FROM track");
return std::vector<std::filesystem::path>(res.begin(), res.end());
Wt::Dbo::collection<QueryResultType> queryRes = session.getDboSession().query<QueryResultType>("SELECT id,file_path FROM track")
.limit(size ? static_cast<int>(*size) + 1 : -1)
.offset(offset ? static_cast<int>(*offset) : -1);
std::vector<std::pair<IdType, std::filesystem::path>> result;
result.reserve(queryRes.size());
std::transform(std::begin(queryRes), std::end(queryRes), std::back_inserter(result),
[](const QueryResultType& queryResult)
{
return std::make_pair(std::get<0>(queryResult), std::get<1>(queryResult));
});
return result;
}
std::vector<Track::pointer>
+11 -1
View File
@@ -55,6 +55,7 @@ class Track : public Wt::Dbo::Dbo<Track>
Track(const std::filesystem::path& p);
// Find utility functions
static std::size_t getCount(Session& session);
static pointer getByPath(Session& session, const std::filesystem::path& p);
static pointer getById(Session& session, IdType id);
static pointer getByMBID(Session& session, const UUID& MBID);
@@ -74,7 +75,7 @@ class Track : public Wt::Dbo::Dbo<Track>
static std::vector<pointer> getAll(Session& session, std::optional<std::size_t> limit = {});
static std::vector<pointer> getAllRandom(Session& session, std::optional<std::size_t> limit = {});
static std::vector<IdType> getAllIds(Session& session);
static std::vector<std::filesystem::path> getAllPaths(Session& session);
static std::vector<std::pair<IdType, std::filesystem::path>> getAllPaths(Session& session, std::optional<std::size_t> offset = std::nullopt, std::optional<std::size_t> size = std::nullopt);
static std::vector<pointer> getMBIDDuplicates(Session& session);
static std::vector<pointer> getLastAdded(Session& session, const Wt::WDateTime& after, std::optional<std::size_t> size = 1);
static std::vector<pointer> getAllWithMBIDAndMissingFeatures(Session& session);
@@ -100,6 +101,8 @@ class Track : public Wt::Dbo::Dbo<Track>
void setMBID(const std::optional<UUID>& MBID) { _MBID = MBID ? MBID->getAsString() : ""; }
void setCopyright(const std::string& copyright) { _copyright = std::string(copyright, 0, _maxCopyrightLength); }
void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); }
void setTrackReplayGain(float replayGain) { _trackReplayGain = replayGain; }
void setReleaseReplayGain(float replayGain) { _releaseReplayGain = replayGain; }
void clearArtistLinks();
void addArtistLink(const Wt::Dbo::ptr<TrackArtistLink>& artistLink);
void setRelease(Wt::Dbo::ptr<Release> release) { _release = release; }
@@ -122,6 +125,9 @@ class Track : public Wt::Dbo::Dbo<Track>
std::optional<UUID> getMBID() const { return UUID::fromString(_MBID); }
std::optional<std::string> getCopyright() const;
std::optional<std::string> getCopyrightURL() const;
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
std::vector<Wt::Dbo::ptr<Artist>> getArtists(TrackArtistLink::Type type = TrackArtistLink::Type::Artist) const;
std::vector<IdType> getArtistIds(TrackArtistLink::Type type = TrackArtistLink::Type::Artist) const;
std::vector<Wt::Dbo::ptr<TrackArtistLink>> getArtistLinks() const;
@@ -152,6 +158,8 @@ class Track : public Wt::Dbo::Dbo<Track>
Wt::Dbo::field(a, _MBID, "mbid");
Wt::Dbo::field(a, _copyright, "copyright");
Wt::Dbo::field(a, _copyrightURL, "copyright_url");
Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain");
Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain");
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track");
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
@@ -184,6 +192,8 @@ class Track : public Wt::Dbo::Dbo<Track>
std::string _MBID; // Musicbrainz Identifier
std::string _copyright;
std::string _copyrightURL;
std::optional<float> _trackReplayGain;
std::optional<float> _releaseReplayGain;
Wt::Dbo::ptr<Release> _release;
Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks;
+37 -3
View File
@@ -19,13 +19,16 @@
#include "metadata/TagLibParser.hpp"
#include <taglib/apetag.h>
#include <taglib/asffile.h>
#include <taglib/id3v2tag.h>
#include <taglib/fileref.h>
#include <taglib/flacfile.h>
#include <taglib/mpcfile.h>
#include <taglib/mpegfile.h>
#include <taglib/tag.h>
#include <taglib/tpropertymap.h>
#include <taglib/wavpackfile.h>
#include "utils/Logger.hpp"
#include "utils/String.hpp"
@@ -263,6 +266,10 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
track.copyright = value;
else if (tag == "COPYRIGHTURL")
track.copyrightURL = value;
else if (tag == "REPLAYGAIN_ALBUM_GAIN")
track.albumReplayGain = StringUtils::readAs<float>(value);
else if (tag == "REPLAYGAIN_TRACK_GAIN")
track.trackReplayGain = StringUtils::readAs<float>(value);
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{
std::set<std::string> clusterNames;
@@ -311,6 +318,21 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
TagLib::PropertyMap properties {f.file()->properties()};
auto getAPETags = [&](const TagLib::APE::Tag* apeTag)
{
if (!apeTag)
return;
for (const auto& [name, values] : apeTag->properties())
{
if (debug)
std::cout << "APE property: '" << name << "'" << std::endl;
if (!properties.contains(name))
properties.insert(name, values);
}
};
// Not that good embedded pictures handling
// WMA
@@ -337,9 +359,10 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
if (!stringAttributeList.isEmpty())
{
if (debug)
std::cout << "Property: '" << name << "'" << std::endl;
std::cout << "ASF property: '" << name << "'" << std::endl;
properties.insert(name, stringAttributeList);
if (!properties.contains(name))
properties.insert(name, stringAttributeList);
}
}
}
@@ -352,6 +375,17 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
if (!mp3File->ID3v2Tag()->frameListMap()["APIC"].isEmpty())
track.hasCover = true;
}
getAPETags(mp3File->APETag());
}
else if (TagLib::MPC::File* mpcFile {dynamic_cast<TagLib::MPC::File*>(f.file())})
{
getAPETags(mpcFile->APETag());
}
// WavPack
else if (TagLib::WavPack::File* wavPackFile {dynamic_cast<TagLib::WavPack::File*>(f.file())})
{
getAPETags(wavPackFile->APETag());
}
// FLAC
else if (TagLib::FLAC::File* flacFile {dynamic_cast<TagLib::FLAC::File*>(f.file())})
@@ -360,7 +394,7 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
track.hasCover = true;
}
for(const auto& property : properties)
for (const auto& property : properties)
{
const std::string tag {property.first.upper().to8Bit(true)};
const TagLib::StringList& values {property.second};
@@ -75,6 +75,8 @@ namespace MetaData
std::optional<UUID> acoustID;
std::string copyright;
std::string copyrightURL;
std::optional<float> trackReplayGain;
std::optional<float> albumReplayGain;
};
class IParser
+44 -12
View File
@@ -111,7 +111,6 @@ updateArtistIfNeeded(const Artist::pointer& artist, const MetaData::Artist& arti
// Sortname may have been updated
if (artistInfo.sortName && *artistInfo.sortName != artist->getSortName() )
{
LMS_LOG(DBUPDATER, INFO) << "Setting sort name = '" << *artistInfo.sortName << "'";
artist.modify()->setSortName(*artistInfo.sortName);
}
}
@@ -751,6 +750,10 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S
track.modify()->setHasCover(trackInfo->hasCover);
track.modify()->setCopyright(trackInfo->copyright);
track.modify()->setCopyrightURL(trackInfo->copyrightURL);
if (trackInfo->trackReplayGain)
track.modify()->setTrackReplayGain(*trackInfo->trackReplayGain);
if (trackInfo->albumReplayGain)
track.modify()->setReleaseReplayGain(*trackInfo->albumReplayGain);
}
void
@@ -819,32 +822,61 @@ checkFile(const std::filesystem::path& p, const std::filesystem::path& mediaDire
void
MediaScanner::removeMissingTracks(ScanStats& stats)
{
std::vector<std::filesystem::path> trackPaths;
static constexpr std::size_t batchSize {50};
LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks to be removed...";
std::size_t trackCount {};
{
auto transaction {_dbSession.createSharedTransaction()};
trackPaths = Track::getAllPaths(_dbSession);;
trackCount = Track::getCount(_dbSession);
}
LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks to be checked...";
LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks...";
for (const auto& trackPath : trackPaths)
std::vector<std::pair<Database::IdType, std::filesystem::path>> trackPaths;
std::vector<IdType> tracksToRemove;
for (std::size_t i {trackCount < batchSize ? 0 : trackCount - batchSize}; ; i -= (i > batchSize ? batchSize : i))
{
if (!_running)
return;
trackPaths.clear();
tracksToRemove.clear();
if (!checkFile(trackPath, _mediaDirectory, _fileExtensions))
{
auto transaction {_dbSession.createSharedTransaction()};
trackPaths = Track::getAllPaths(_dbSession, i, batchSize);
}
for (const auto& [trackId, trackPath] : trackPaths)
{
if (!_running)
return;
if (!checkFile(trackPath, _mediaDirectory, _fileExtensions))
tracksToRemove.push_back(trackId);
}
if (!tracksToRemove.empty())
{
auto transaction {_dbSession.createUniqueTransaction()};
Track::pointer track {Track::getByPath(_dbSession, trackPath)};
if (track)
for (const IdType trackId : tracksToRemove)
{
track.remove();
stats.deletions++;
Track::pointer track {Track::getById(_dbSession, trackId)};
if (track)
{
track.remove();
stats.deletions++;
}
}
}
notifyInProgressIfNeeded(stats);
if (i == 0)
break;
}
LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks checked!";
}
void