Merge branch 'develop'
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
# LMS - Lightweight Music Server
|
# LMS - Lightweight Music Server
|
||||||
|
|
||||||
LMS is a self-hosted music streaming software, released under the GPLv3 license.
|
LMS is a self-hosted music streaming software: access your music files from anywhere using a web interface!
|
||||||
It allows you to access your music using a web interface.
|
|
||||||
|
|
||||||
A [demo](http://lms.demo.poupon.io) instance is available, with the following limitations:
|
A [demo](http://lms.demo.poupon.io) instance is available, with the following limitations:
|
||||||
- Settings cannot be saved
|
- Settings cannot be saved
|
||||||
@@ -10,8 +9,8 @@ A [demo](http://lms.demo.poupon.io) instance is available, with the following li
|
|||||||
## Main features
|
## Main features
|
||||||
- Responsive design
|
- Responsive design
|
||||||
- Browse your audio collection using tag-based filters
|
- Browse your audio collection using tag-based filters
|
||||||
- User management
|
|
||||||
- Persistent play queue
|
- Persistent play queue
|
||||||
|
- User management
|
||||||
- Radio mode
|
- Radio mode
|
||||||
- MusicBrainzID support to handle duplicated artist and release names
|
- MusicBrainzID support to handle duplicated artist and release names
|
||||||
- Audio transcode for maximum interoperability and low bandwith requirements
|
- Audio transcode for maximum interoperability and low bandwith requirements
|
||||||
|
|||||||
@@ -79,6 +79,9 @@
|
|||||||
<message id="Lms.Explore.ArtistsInfo.recently-added">Recently added</message>
|
<message id="Lms.Explore.ArtistsInfo.recently-added">Recently added</message>
|
||||||
<message id="Lms.Explore.ArtistsInfo.most-played">Top artists</message>
|
<message id="Lms.Explore.ArtistsInfo.most-played">Top artists</message>
|
||||||
|
|
||||||
|
<!--Explore:Release-->
|
||||||
|
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
||||||
|
|
||||||
<!--Explore:Releases-->
|
<!--Explore:Releases-->
|
||||||
<message id="Lms.Explore.ReleasesInfo.recently-added">Recently added</message>
|
<message id="Lms.Explore.ReleasesInfo.recently-added">Recently added</message>
|
||||||
<message id="Lms.Explore.ReleasesInfo.most-played">Top releases</message>
|
<message id="Lms.Explore.ReleasesInfo.most-played">Top releases</message>
|
||||||
|
|||||||
@@ -16,6 +16,13 @@
|
|||||||
${play-btn}${add-btn}
|
${play-btn}${add-btn}
|
||||||
</div>
|
</div>
|
||||||
${tracks}
|
${tracks}
|
||||||
|
${<if-has-copyright-or-copyright-url>}
|
||||||
|
<div class="page-header">
|
||||||
|
<h4><small>${tr:Lms.Explore.Release.copyright}</small></h4>
|
||||||
|
</div>
|
||||||
|
${<if-has-copyright>}<small>${copyright}</small>${</if-has-copyright>}
|
||||||
|
${<if-has-copyright-url>}<small>${copyright-url}</small>${</if-has-copyright-url>}
|
||||||
|
${</if-has-copyright-or-copyright-url>}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Release.template.entry">
|
<message id="Lms.Explore.Release.template.entry">
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ struct TranscodeParameters
|
|||||||
Encoding encoding = Encoding::MP3;
|
Encoding encoding = Encoding::MP3;
|
||||||
std::size_t bitrate = 128000;
|
std::size_t bitrate = 128000;
|
||||||
boost::optional<std::size_t> stream = boost::none; // Id of the stream to be transcoded (auto detect by default)
|
boost::optional<std::size_t> stream = boost::none; // Id of the stream to be transcoded (auto detect by default)
|
||||||
boost::optional<std::chrono::seconds> offset;
|
boost::optional<std::chrono::seconds> offset = boost::none;;
|
||||||
|
|
||||||
TranscodeParameters() {}
|
TranscodeParameters() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Transcoder
|
class Transcoder
|
||||||
|
|||||||
@@ -178,12 +178,12 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::IdType trackId, std::
|
|||||||
Track::pointer track = Track::getById(session, trackId);
|
Track::pointer track = Track::getById(session, trackId);
|
||||||
if (track)
|
if (track)
|
||||||
{
|
{
|
||||||
Track::CoverType coverType = track->getCoverType();
|
bool hasCover = track->hasCover();
|
||||||
boost::filesystem::path trackPath = track->getPath();
|
boost::filesystem::path trackPath = track->getPath();
|
||||||
|
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
|
|
||||||
if (coverType == Track::CoverType::Embedded)
|
if (hasCover)
|
||||||
cover = getFromTrack(trackPath);
|
cover = getFromTrack(trackPath);
|
||||||
|
|
||||||
if (!cover)
|
if (!cover)
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::st
|
|||||||
std::vector<Artist::pointer>
|
std::vector<Artist::pointer>
|
||||||
Artist::getAll(Wt::Dbo::Session& session, int offset, int size)
|
Artist::getAll(Wt::Dbo::Session& session, int offset, int size)
|
||||||
{
|
{
|
||||||
Wt::Dbo::collection<pointer> res = session.find<Artist>().orderBy("LOWER(name)").offset(offset).limit(size);
|
Wt::Dbo::collection<pointer> res = session.find<Artist>().offset(offset).limit(size);
|
||||||
return std::vector<pointer>(res.begin(), res.end());
|
return std::vector<pointer>(res.begin(), res.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ getQuery(Wt::Dbo::Session& session,
|
|||||||
if (!clusterIds.empty())
|
if (!clusterIds.empty())
|
||||||
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
|
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
|
||||||
|
|
||||||
oss << " ORDER BY a.name";
|
oss << " ORDER BY a.name COLLATE NOCASE";
|
||||||
|
|
||||||
Wt::Dbo::Query<Artist::pointer> query = session.query<Artist::pointer>( oss.str() );
|
Wt::Dbo::Query<Artist::pointer> query = session.query<Artist::pointer>( oss.str() );
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "Release.hpp"
|
#include "Release.hpp"
|
||||||
|
|
||||||
|
#include "utils/Logger.hpp"
|
||||||
|
|
||||||
#include "Artist.hpp"
|
#include "Artist.hpp"
|
||||||
#include "Cluster.hpp"
|
#include "Cluster.hpp"
|
||||||
#include "SqlQuery.hpp"
|
#include "SqlQuery.hpp"
|
||||||
@@ -117,7 +119,7 @@ getQuery(Wt::Dbo::Session& session,
|
|||||||
if (!clusterIds.empty())
|
if (!clusterIds.empty())
|
||||||
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
|
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
|
||||||
|
|
||||||
oss << " ORDER BY r.name";
|
oss << " ORDER BY r.name COLLATE NOCASE";
|
||||||
|
|
||||||
Wt::Dbo::Query<Release::pointer> query = session.query<Release::pointer>( oss.str() );
|
Wt::Dbo::Query<Release::pointer> query = session.query<Release::pointer>( oss.str() );
|
||||||
|
|
||||||
@@ -163,7 +165,7 @@ Release::getReleaseYear(bool original) const
|
|||||||
.groupBy(field)
|
.groupBy(field)
|
||||||
.bind(this->id());
|
.bind(this->id());
|
||||||
|
|
||||||
/* various dates, no date */
|
// various dates => no date
|
||||||
if (dates.empty() || dates.size() > 1)
|
if (dates.empty() || dates.size() > 1)
|
||||||
return boost::none;
|
return boost::none;
|
||||||
|
|
||||||
@@ -175,6 +177,46 @@ Release::getReleaseYear(bool original) const
|
|||||||
return boost::none;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::optional<std::string>
|
||||||
|
Release::getCopyright() const
|
||||||
|
{
|
||||||
|
assert(session());
|
||||||
|
|
||||||
|
Wt::Dbo::collection<std::string> copyrights = session()->query<std::string>
|
||||||
|
("SELECT copyright FROM track t INNER JOIN release r ON r.id = t.release_id")
|
||||||
|
.where("r.id = ?")
|
||||||
|
.groupBy("copyright")
|
||||||
|
.bind(this->id());
|
||||||
|
|
||||||
|
std::vector<std::string> values(copyrights.begin(), copyrights.end());
|
||||||
|
|
||||||
|
// various copyrights => no copyright
|
||||||
|
if (values.empty() || values.size() > 1 || values.front().empty())
|
||||||
|
return boost::none;
|
||||||
|
|
||||||
|
return values.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::string>
|
||||||
|
Release::getCopyrightURL() const
|
||||||
|
{
|
||||||
|
assert(session());
|
||||||
|
|
||||||
|
Wt::Dbo::collection<std::string> copyrights = session()->query<std::string>
|
||||||
|
("SELECT copyright_url FROM track t INNER JOIN release r ON r.id = t.release_id")
|
||||||
|
.where("r.id = ?")
|
||||||
|
.groupBy("copyright_url")
|
||||||
|
.bind(this->id());
|
||||||
|
|
||||||
|
std::vector<std::string> values(copyrights.begin(), copyrights.end());
|
||||||
|
|
||||||
|
// various copyright URLs => no copyright URL
|
||||||
|
if (values.empty() || values.size() > 1 || values.front().empty())
|
||||||
|
return boost::none;
|
||||||
|
|
||||||
|
return values.front();
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Wt::Dbo::ptr<Artist>>
|
std::vector<Wt::Dbo::ptr<Artist>>
|
||||||
Release::getArtists() const
|
Release::getArtists() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class Release : public Wt::Dbo::Dbo<Release>
|
|||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
boost::optional<int> getReleaseYear(bool originalDate = false) const; // 0 if unknown or various
|
boost::optional<int> getReleaseYear(bool originalDate = false) const; // 0 if unknown or various
|
||||||
|
boost::optional<std::string> getCopyright() const;
|
||||||
|
boost::optional<std::string> getCopyrightURL() const;
|
||||||
|
|
||||||
// Accessors
|
// Accessors
|
||||||
std::string getName() const { return _name; }
|
std::string getName() const { return _name; }
|
||||||
|
|||||||
+13
-1
@@ -146,7 +146,7 @@ getQuery(Wt::Dbo::Session& session,
|
|||||||
if (!clusterIds.empty())
|
if (!clusterIds.empty())
|
||||||
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
|
oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
|
||||||
|
|
||||||
oss << " ORDER BY t.name";
|
oss << " ORDER BY t.name COLLATE NOCASE";
|
||||||
|
|
||||||
Wt::Dbo::Query<Track::pointer> query = session.query<Track::pointer>( oss.str() );
|
Wt::Dbo::Query<Track::pointer> query = session.query<Track::pointer>( oss.str() );
|
||||||
|
|
||||||
@@ -224,6 +224,18 @@ Track::getOriginalYear() const
|
|||||||
return (_originalYear > 0) ? boost::make_optional<int>(_originalYear) : boost::none;
|
return (_originalYear > 0) ? boost::make_optional<int>(_originalYear) : boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::optional<std::string>
|
||||||
|
Track::getCopyright() const
|
||||||
|
{
|
||||||
|
return _copyright != "" ? boost::make_optional<std::string>(_copyright) : boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::optional<std::string>
|
||||||
|
Track::getCopyrightURL() const
|
||||||
|
{
|
||||||
|
return _copyrightURL != "" ? boost::make_optional<std::string>(_copyrightURL) : boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::vector<Cluster::pointer>>
|
std::vector<std::vector<Cluster::pointer>>
|
||||||
Track::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const
|
Track::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const
|
||||||
{
|
{
|
||||||
|
|||||||
+15
-11
@@ -49,12 +49,6 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
Track() {}
|
Track() {}
|
||||||
Track(const boost::filesystem::path& p);
|
Track(const boost::filesystem::path& p);
|
||||||
|
|
||||||
enum class CoverType
|
|
||||||
{
|
|
||||||
Embedded, // Contains embedded cover
|
|
||||||
None, // No local cover available
|
|
||||||
};
|
|
||||||
|
|
||||||
// Find utility functions
|
// Find utility functions
|
||||||
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
|
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
|
||||||
static pointer getById(Wt::Dbo::Session& session, IdType id);
|
static pointer getById(Wt::Dbo::Session& session, IdType id);
|
||||||
@@ -95,8 +89,10 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
void setYear(int year) { _year = year; }
|
void setYear(int year) { _year = year; }
|
||||||
void setOriginalYear(int year) { _originalYear = year; }
|
void setOriginalYear(int year) { _originalYear = year; }
|
||||||
void setGenres(const std::string& genreList) { _genreList = genreList; }
|
void setGenres(const std::string& genreList) { _genreList = genreList; }
|
||||||
void setCoverType(CoverType coverType) { _coverType = coverType; }
|
void setHasCover(bool hasCover) { _hasCover = hasCover; }
|
||||||
void setMBID(const std::string& MBID) { _MBID = MBID; }
|
void setMBID(const std::string& MBID) { _MBID = MBID; }
|
||||||
|
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 setArtist(Wt::Dbo::ptr<Artist> artist) { _artist = artist; }
|
void setArtist(Wt::Dbo::ptr<Artist> artist) { _artist = artist; }
|
||||||
void setRelease(Wt::Dbo::ptr<Release> release) { _release = release; }
|
void setRelease(Wt::Dbo::ptr<Release> release) { _release = release; }
|
||||||
|
|
||||||
@@ -113,8 +109,10 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; }
|
Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; }
|
||||||
Wt::WDateTime getAddedTime() const { return _fileAdded; }
|
Wt::WDateTime getAddedTime() const { return _fileAdded; }
|
||||||
const std::vector<unsigned char>& getChecksum() const { return _fileChecksum; }
|
const std::vector<unsigned char>& getChecksum() const { return _fileChecksum; }
|
||||||
CoverType getCoverType() const { return _coverType; }
|
bool hasCover() const { return _hasCover; }
|
||||||
const std::string& getMBID() const { return _MBID; }
|
const std::string& getMBID() const { return _MBID; }
|
||||||
|
boost::optional<std::string> getCopyright() const;
|
||||||
|
boost::optional<std::string> getCopyrightURL() const;
|
||||||
Wt::Dbo::ptr<Artist> getArtist() const { return _artist; }
|
Wt::Dbo::ptr<Artist> getArtist() const { return _artist; }
|
||||||
Wt::Dbo::ptr<Release> getRelease() const { return _release; }
|
Wt::Dbo::ptr<Release> getRelease() const { return _release; }
|
||||||
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
|
std::vector<Wt::Dbo::ptr<Cluster>> getClusters() const;
|
||||||
@@ -138,8 +136,10 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
Wt::Dbo::field(a, _fileLastWrite, "file_last_write");
|
Wt::Dbo::field(a, _fileLastWrite, "file_last_write");
|
||||||
Wt::Dbo::field(a, _fileAdded, "file_added");
|
Wt::Dbo::field(a, _fileAdded, "file_added");
|
||||||
Wt::Dbo::field(a, _fileChecksum, "checksum");
|
Wt::Dbo::field(a, _fileChecksum, "checksum");
|
||||||
Wt::Dbo::field(a, _coverType, "cover_type");
|
Wt::Dbo::field(a, _hasCover, "has_cover");
|
||||||
Wt::Dbo::field(a, _MBID, "mbid");
|
Wt::Dbo::field(a, _MBID, "mbid");
|
||||||
|
Wt::Dbo::field(a, _copyright, "copyright");
|
||||||
|
Wt::Dbo::field(a, _copyrightURL, "copyright_url");
|
||||||
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
||||||
Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade);
|
||||||
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
||||||
@@ -149,6 +149,8 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
static const std::size_t _maxNameLength = 128;
|
static const std::size_t _maxNameLength = 128;
|
||||||
|
static const std::size_t _maxCopyrightLength = 128;
|
||||||
|
static const std::size_t _maxCopyrightURLLength = 128;
|
||||||
|
|
||||||
int _scanVersion = 0;
|
int _scanVersion = 0;
|
||||||
int _trackNumber = 0;
|
int _trackNumber = 0;
|
||||||
@@ -166,8 +168,10 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
std::vector<unsigned char> _fileChecksum;
|
std::vector<unsigned char> _fileChecksum;
|
||||||
Wt::WDateTime _fileLastWrite;
|
Wt::WDateTime _fileLastWrite;
|
||||||
Wt::WDateTime _fileAdded;
|
Wt::WDateTime _fileAdded;
|
||||||
CoverType _coverType = CoverType::None;
|
bool _hasCover = false;
|
||||||
std::string _MBID = ""; // Musicbrainz Identifier
|
std::string _MBID; // Musicbrainz Identifier
|
||||||
|
std::string _copyright;
|
||||||
|
std::string _copyrightURL;
|
||||||
|
|
||||||
Wt::Dbo::ptr<Artist> _artist;
|
Wt::Dbo::ptr<Artist> _artist;
|
||||||
Wt::Dbo::ptr<Release> _release;
|
Wt::Dbo::ptr<Release> _release;
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ namespace MetaData
|
|||||||
MusicBrainzTrackID, // string
|
MusicBrainzTrackID, // string
|
||||||
MusicBrainzRecordingID, // string
|
MusicBrainzRecordingID, // string
|
||||||
AcoustID, // string
|
AcoustID, // string
|
||||||
|
Copyright, // string
|
||||||
|
CopyrightURL, // string
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used by Streams
|
// Used by Streams
|
||||||
|
|||||||
@@ -194,11 +194,24 @@ TagLibParser::parse(const boost::filesystem::path& p, bool debug)
|
|||||||
if (items.find(MetaData::Type::HasCover) == items.end())
|
if (items.find(MetaData::Type::HasCover) == items.end())
|
||||||
items.insert( std::make_pair(MetaData::Type::HasCover, true));
|
items.insert( std::make_pair(MetaData::Type::HasCover, true));
|
||||||
}
|
}
|
||||||
|
else if (tag == "COPYRIGHT")
|
||||||
|
{
|
||||||
|
items.insert(std::make_pair(MetaData::Type::Copyright, values.front().to8Bit()));
|
||||||
|
}
|
||||||
|
else if (tag == "COPYRIGHTURL")
|
||||||
|
{
|
||||||
|
items.insert(std::make_pair(MetaData::Type::CopyrightURL, values.front().to8Bit()));
|
||||||
|
}
|
||||||
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||||
{
|
{
|
||||||
std::set<std::string> clusterNames;
|
std::set<std::string> clusterNames;
|
||||||
for (const auto& value : values)
|
for (const auto& valueList : values)
|
||||||
clusterNames.insert(value.to8Bit(true));
|
{
|
||||||
|
auto values = splitString(valueList.to8Bit(), "/,;");
|
||||||
|
|
||||||
|
for (auto value : values)
|
||||||
|
clusterNames.insert(value);
|
||||||
|
}
|
||||||
|
|
||||||
if (!clusterNames.empty())
|
if (!clusterNames.empty())
|
||||||
clusters[tag] = clusterNames;
|
clusters[tag] = clusterNames;
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ void
|
|||||||
MediaScanner::scheduleScan(std::chrono::seconds duration)
|
MediaScanner::scheduleScan(std::chrono::seconds duration)
|
||||||
{
|
{
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan in " << duration.count() << " seconds";
|
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan in " << duration.count() << " seconds";
|
||||||
_scheduleTimer.expires_from_now(std::chrono::seconds(5)); //duration);
|
_scheduleTimer.expires_from_now(duration);
|
||||||
_scheduleTimer.async_wait( std::bind( &MediaScanner::scan, this, std::placeholders::_1) );
|
_scheduleTimer.async_wait( std::bind( &MediaScanner::scan, this, std::placeholders::_1) );
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ MediaScanner::scan(boost::system::error_code err)
|
|||||||
bool forceScan = false;
|
bool forceScan = false;
|
||||||
Stats stats;
|
Stats stats;
|
||||||
|
|
||||||
checkAudioFiles(stats);
|
removeMissingTracks(stats);
|
||||||
|
|
||||||
LMS_LOG(UI, INFO) << "Checks complete, force scan = " << forceScan;
|
LMS_LOG(UI, INFO) << "Checks complete, force scan = " << forceScan;
|
||||||
|
|
||||||
@@ -325,7 +325,10 @@ MediaScanner::scan(boost::system::error_code err)
|
|||||||
LMS_LOG(DBUPDATER, INFO) << "scaning media directory '" << _mediaDirectory.string() << "' DONE";
|
LMS_LOG(DBUPDATER, INFO) << "scaning media directory '" << _mediaDirectory.string() << "' DONE";
|
||||||
|
|
||||||
if (_running)
|
if (_running)
|
||||||
|
{
|
||||||
|
removeOrphanEntries();
|
||||||
checkDuplicatedAudioFiles(stats);
|
checkDuplicatedAudioFiles(stats);
|
||||||
|
}
|
||||||
|
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Scan " << (_running ? "complete" : "aborted") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.scanErrors << ", not imported = " << stats.incompleteScans << "), duplicates = " << stats.nbDuplicates() << " (hash = " << stats.duplicateHashes << ", mbid = " << stats.duplicateMBID << ")";
|
LMS_LOG(DBUPDATER, INFO) << "Scan " << (_running ? "complete" : "aborted") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.scanErrors << ", not imported = " << stats.incompleteScans << "), duplicates = " << stats.nbDuplicates() << " (hash = " << stats.duplicateHashes << ", mbid = " << stats.duplicateMBID << ")";
|
||||||
|
|
||||||
@@ -566,7 +569,18 @@ MediaScanner::scanAudioFile(const boost::filesystem::path& file, bool forceScan,
|
|||||||
{
|
{
|
||||||
bool hasCover = boost::any_cast<bool>((*items)[MetaData::Type::HasCover]);
|
bool hasCover = boost::any_cast<bool>((*items)[MetaData::Type::HasCover]);
|
||||||
|
|
||||||
track.modify()->setCoverType( hasCover ? Track::CoverType::Embedded : Track::CoverType::None );
|
track.modify()->setHasCover(hasCover);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*items).find(MetaData::Type::Copyright) != (*items).end())
|
||||||
|
{
|
||||||
|
|
||||||
|
track.modify()->setCopyright( boost::any_cast<std::string>((*items)[MetaData::Type::Copyright]) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*items).find(MetaData::Type::CopyrightURL) != (*items).end())
|
||||||
|
{
|
||||||
|
track.modify()->setCopyrightURL( boost::any_cast<std::string>((*items)[MetaData::Type::CopyrightURL]) );
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
@@ -644,10 +658,8 @@ checkFile(const boost::filesystem::path& p, boost::filesystem::path mediaDirecto
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::checkAudioFiles( Stats& stats )
|
MediaScanner::removeMissingTracks(Stats& stats)
|
||||||
{
|
{
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Checking audio files...";
|
|
||||||
|
|
||||||
std::vector<boost::filesystem::path> trackPaths = Track::getAllPaths(_db.getSession());;
|
std::vector<boost::filesystem::path> trackPaths = Track::getAllPaths(_db.getSession());;
|
||||||
|
|
||||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks...";
|
LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks...";
|
||||||
@@ -668,7 +680,11 @@ MediaScanner::checkAudioFiles( Stats& stats )
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MediaScanner::removeOrphanEntries()
|
||||||
|
{
|
||||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters...";
|
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters...";
|
||||||
{
|
{
|
||||||
Wt::Dbo::Transaction transaction(_db.getSession());
|
Wt::Dbo::Transaction transaction(_db.getSession());
|
||||||
|
|||||||
@@ -83,7 +83,8 @@ class MediaScanner
|
|||||||
// Helpers
|
// Helpers
|
||||||
void refreshScanSettings();
|
void refreshScanSettings();
|
||||||
|
|
||||||
void checkAudioFiles( Stats& stats );
|
void removeMissingTracks( Stats& stats );
|
||||||
|
void removeOrphanEntries();
|
||||||
void checkDuplicatedAudioFiles( Stats& stats );
|
void checkDuplicatedAudioFiles( Stats& stats );
|
||||||
void scanAudioFile( const boost::filesystem::path& file, bool forceScan, Stats& stats);
|
void scanAudioFile( const boost::filesystem::path& file, bool forceScan, Stats& stats);
|
||||||
|
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ DatabaseSettingsView::refreshView()
|
|||||||
Wt::WPushButton *discardBtn = t->bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard")));
|
Wt::WPushButton *discardBtn = t->bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard")));
|
||||||
Wt::WPushButton *immScanBtn = t->bindWidget("immediate-scan-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.Admin.Database.immediate-scan")));
|
Wt::WPushButton *immScanBtn = t->bindWidget("immediate-scan-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.Admin.Database.immediate-scan")));
|
||||||
|
|
||||||
saveBtn->clicked().connect(std::bind([=] ()
|
saveBtn->clicked().connect([=] ()
|
||||||
{
|
{
|
||||||
t->updateModel(model.get());
|
t->updateModel(model.get());
|
||||||
|
|
||||||
@@ -293,20 +293,20 @@ DatabaseSettingsView::refreshView()
|
|||||||
|
|
||||||
// Udate the view: Delete any validation message in the view, etc.
|
// Udate the view: Delete any validation message in the view, etc.
|
||||||
t->updateView(model.get());
|
t->updateView(model.get());
|
||||||
}));
|
});
|
||||||
|
|
||||||
discardBtn->clicked().connect(std::bind([=] ()
|
discardBtn->clicked().connect([=] ()
|
||||||
{
|
{
|
||||||
model->loadData();
|
model->loadData();
|
||||||
model->validate();
|
model->validate();
|
||||||
t->updateView(model.get());
|
t->updateView(model.get());
|
||||||
}));
|
});
|
||||||
|
|
||||||
immScanBtn->clicked().connect(std::bind([=] ()
|
immScanBtn->clicked().connect([=] ()
|
||||||
{
|
{
|
||||||
LmsApp->getMediaScanner().scheduleImmediateScan();
|
LmsApp->getMediaScanner().scheduleImmediateScan();
|
||||||
LmsApp->notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-launched"));
|
LmsApp->notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-launched"));
|
||||||
}));
|
});
|
||||||
|
|
||||||
t->updateView(model.get());
|
t->updateView(model.get());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,28 @@ Release::refresh()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::optional<std::string> copyright = release->getCopyright();
|
||||||
|
boost::optional<std::string> copyrightURL = release->getCopyrightURL();
|
||||||
|
|
||||||
|
t->setCondition("if-has-copyright-or-copyright-url", copyright || copyrightURL);
|
||||||
|
|
||||||
|
if (copyrightURL)
|
||||||
|
{
|
||||||
|
t->setCondition("if-has-copyright-url", true);
|
||||||
|
|
||||||
|
Wt::WLink link(*copyrightURL);
|
||||||
|
link.setTarget(Wt::LinkTarget::NewWindow);
|
||||||
|
Wt::WAnchor* anchor = t->bindNew<Wt::WAnchor>("copyright-url", link);
|
||||||
|
anchor->setTextFormat(Wt::TextFormat::Plain);
|
||||||
|
anchor->setText(Wt::WString::fromUTF8(*copyrightURL));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copyright)
|
||||||
|
{
|
||||||
|
t->setCondition("if-has-copyright", true);
|
||||||
|
t->bindString("copyright", Wt::WString::fromUTF8(*copyright), Wt::TextFormat::Plain);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto artists = release->getArtists();
|
auto artists = release->getArtists();
|
||||||
if (artists.size() > 1)
|
if (artists.size() > 1)
|
||||||
|
|||||||
@@ -112,6 +112,14 @@ int main(int argc, char *argv[])
|
|||||||
std::cout << "AcoustID: " << boost::any_cast<std::string>(item.second) << std::endl;
|
std::cout << "AcoustID: " << boost::any_cast<std::string>(item.second) << std::endl;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MetaData::Type::Copyright:
|
||||||
|
std::cout << "Copyright: " << boost::any_cast<std::string>(item.second) << std::endl;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MetaData::Type::CopyrightURL:
|
||||||
|
std::cout << "CopyrightURL: " << boost::any_cast<std::string>(item.second) << std::endl;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user