Added disc subtitles support. Fixes #56

This commit is contained in:
emeric
2020-05-11 18:38:28 +02:00
parent 633395cc60
commit b0c6aa746c
9 changed files with 49 additions and 15 deletions
+2 -1
View File
@@ -15,12 +15,13 @@ A [demo](http://lms.demo.poupon.io) instance is available, with the following li
* User management * User management
* Recommendation engine * Recommendation engine
* Audio transcode for maximum interoperability and low bandwith requirements * Audio transcode for maximum interoperability and low bandwith requirements
* ReplayGain support
* Persistent play queue across sessions * Persistent play queue across sessions
* Compilation support * Compilation support
* Multi-value tags: artists, genres, ... * Multi-value tags: artists, genres, ...
* Custom tags (ex: _mood_, _genre_, _albummood_, _albumgrouping_, ...) * Custom tags (ex: _mood_, _genre_, _albummood_, _albumgrouping_, ...)
* MusicBrainzID support to handle duplicated artist and release names * MusicBrainzID support to handle duplicated artist and release names
* ReplayGain support * Disc subtitles support
* _Systemd_ integration * _Systemd_ integration
* Subsonic API, with the following additional features: * Subsonic API, with the following additional features:
* Playlists * Playlists
+8 -1
View File
@@ -40,7 +40,7 @@
namespace Database { namespace Database {
#define LMS_DATABASE_VERSION 22 #define LMS_DATABASE_VERSION 23
using Version = std::size_t; using Version = std::size_t;
@@ -254,6 +254,13 @@ CREATE TABLE "user_backup" (
// Just increment the scan version of the settings to make the next scheduled scan rescan everything // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion(); ScanSettings::get(*this).modify()->incScanVersion();
} }
else if (version == 22)
{
_session.execute("ALTER TABLE track ADD disc_subtitle TEXT NOT NULL DEFAULT ''");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion();
}
else else
{ {
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration"; LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
@@ -91,6 +91,7 @@ class Track : public Wt::Dbo::Dbo<Track>
void setDiscNumber(int num) { _discNumber = num; } void setDiscNumber(int num) { _discNumber = num; }
void setTotalTrack(std::optional<int> totalTrack) { totalTrack ? _totalTrack = *totalTrack : 0; } void setTotalTrack(std::optional<int> totalTrack) { totalTrack ? _totalTrack = *totalTrack : 0; }
void setTotalDisc(std::optional<int> totalDisc) { totalDisc ? _totalDisc = *totalDisc : 0; } void setTotalDisc(std::optional<int> totalDisc) { totalDisc ? _totalDisc = *totalDisc : 0; }
void setDiscSubtitle(const std::string& name) { _discSubtitle = name; }
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); } void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
void setDuration(std::chrono::milliseconds duration) { _duration = duration; } void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; } void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
@@ -113,6 +114,7 @@ class Track : public Wt::Dbo::Dbo<Track>
std::optional<std::size_t> getTrackNumber() const; std::optional<std::size_t> getTrackNumber() const;
std::optional<std::size_t> getTotalTrack() const; std::optional<std::size_t> getTotalTrack() const;
std::optional<std::size_t> getDiscNumber() const; std::optional<std::size_t> getDiscNumber() const;
const std::string& getDiscSubtitle() const { return _discSubtitle; }
std::optional<std::size_t> getTotalDisc() const; std::optional<std::size_t> getTotalDisc() const;
std::string getName() const { return _name; } std::string getName() const { return _name; }
std::filesystem::path getPath() const { return _filePath; } std::filesystem::path getPath() const { return _filePath; }
@@ -145,6 +147,7 @@ class Track : public Wt::Dbo::Dbo<Track>
Wt::Dbo::field(a, _scanVersion, "scan_version"); Wt::Dbo::field(a, _scanVersion, "scan_version");
Wt::Dbo::field(a, _trackNumber, "track_number"); Wt::Dbo::field(a, _trackNumber, "track_number");
Wt::Dbo::field(a, _discNumber, "disc_number"); Wt::Dbo::field(a, _discNumber, "disc_number");
Wt::Dbo::field(a, _discSubtitle, "disc_subtitle");
Wt::Dbo::field(a, _totalTrack, "total_track"); Wt::Dbo::field(a, _totalTrack, "total_track");
Wt::Dbo::field(a, _totalDisc, "total_disc"); Wt::Dbo::field(a, _totalDisc, "total_disc");
Wt::Dbo::field(a, _name, "name"); Wt::Dbo::field(a, _name, "name");
@@ -177,6 +180,7 @@ class Track : public Wt::Dbo::Dbo<Track>
int _scanVersion {}; int _scanVersion {};
int _trackNumber {}; int _trackNumber {};
int _discNumber {}; int _discNumber {};
std::string _discSubtitle;
int _totalTrack {}; int _totalTrack {};
int _totalDisc {}; int _totalDisc {};
std::string _name; std::string _name;
@@ -211,6 +211,12 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
{ {
track.musicBrainzTrackID = UUID::fromString(value); track.musicBrainzTrackID = UUID::fromString(value);
} }
else if (tag == "TSST"
|| tag == "DISCSUBTITLE"
|| tag == "SETSUBTITLE")
{
track.discSubtitle = value;
}
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end()) else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{ {
std::vector<std::string> clusterNames {StringUtils::splitString(value, "/,;")}; std::vector<std::string> clusterNames {StringUtils::splitString(value, "/,;")};
+7 -1
View File
@@ -270,6 +270,8 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
track.albumReplayGain = StringUtils::readAs<float>(value); track.albumReplayGain = StringUtils::readAs<float>(value);
else if (tag == "REPLAYGAIN_TRACK_GAIN") else if (tag == "REPLAYGAIN_TRACK_GAIN")
track.trackReplayGain = StringUtils::readAs<float>(value); track.trackReplayGain = StringUtils::readAs<float>(value);
else if (tag == "DISCSUBTITLE" || tag == "SETSUBTITLE")
track.discSubtitle = value;
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end()) else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{ {
std::set<std::string> clusterNames; std::set<std::string> clusterNames;
@@ -372,8 +374,12 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
{ {
if (mp3File->ID3v2Tag()) if (mp3File->ID3v2Tag())
{ {
if (!mp3File->ID3v2Tag()->frameListMap()["APIC"].isEmpty()) const auto& frameListMap {mp3File->ID3v2Tag()->frameListMap()};
if (!frameListMap["APIC"].isEmpty())
track.hasCover = true; track.hasCover = true;
if (!frameListMap["TSST"].isEmpty())
properties.insert("DISCSUBTITLE", frameListMap["TSST"].front()->toString());
} }
getAPETags(mp3File->APETag()); getAPETags(mp3File->APETag());
+10 -9
View File
@@ -58,25 +58,26 @@ namespace MetaData
{ {
std::vector<Artist> artists; std::vector<Artist> artists;
std::vector<Artist> albumArtists; std::vector<Artist> albumArtists;
std::string title; std::string title;
std::optional<UUID> musicBrainzTrackID; std::optional<UUID> musicBrainzTrackID;
std::optional<UUID> musicBrainzRecordID; std::optional<UUID> musicBrainzRecordID;
std::optional<Album> album; std::optional<Album> album;
Clusters clusters; Clusters clusters;
std::chrono::milliseconds duration {}; std::chrono::milliseconds duration;
std::optional<std::size_t> trackNumber; std::optional<std::size_t> trackNumber;
std::optional<std::size_t> totalTrack; std::optional<std::size_t> totalTrack;
std::optional<std::size_t> discNumber; std::optional<std::size_t> discNumber;
std::optional<std::size_t> totalDisc; std::optional<std::size_t> totalDisc;
std::optional<int> year; std::optional<int> year;
std::optional<int> originalYear; std::optional<int> originalYear;
bool hasCover {false}; bool hasCover {};
std::vector<AudioStream> audioStreams; std::vector<AudioStream> audioStreams;
std::optional<UUID> acoustID; std::optional<UUID> acoustID;
std::string copyright; std::string copyright;
std::string copyrightURL; std::string copyrightURL;
std::optional<float> trackReplayGain; std::optional<float> trackReplayGain;
std::optional<float> albumReplayGain; std::optional<float> albumReplayGain;
std::string discSubtitle;
}; };
class IParser class IParser
+2
View File
@@ -738,6 +738,8 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S
track.modify()->setDiscNumber(trackInfo->discNumber ? *trackInfo->discNumber : 0); track.modify()->setDiscNumber(trackInfo->discNumber ? *trackInfo->discNumber : 0);
track.modify()->setTotalTrack(trackInfo->totalTrack); track.modify()->setTotalTrack(trackInfo->totalTrack);
track.modify()->setTotalDisc(trackInfo->totalDisc); track.modify()->setTotalDisc(trackInfo->totalDisc);
if (!trackInfo->discSubtitle.empty())
track.modify()->setDiscSubtitle(trackInfo->discSubtitle);
track.modify()->setYear(trackInfo->year ? *trackInfo->year : 0); track.modify()->setYear(trackInfo->year ? *trackInfo->year : 0);
track.modify()->setOriginalYear(trackInfo->originalYear ? *trackInfo->originalYear : 0); track.modify()->setOriginalYear(trackInfo->originalYear ? *trackInfo->originalYear : 0);
+7 -3
View File
@@ -169,7 +169,7 @@ Release::refreshView()
// Expect to be call in asc order // Expect to be call in asc order
std::map<std::size_t, Wt::WContainerWidget*> trackContainers; std::map<std::size_t, Wt::WContainerWidget*> trackContainers;
auto getOrAddDiscContainer = [&](std::size_t discNumber) -> Wt::WContainerWidget* auto getOrAddDiscContainer = [&](std::size_t discNumber, const std::string& discSubtitle) -> Wt::WContainerWidget*
{ {
{ {
auto it = trackContainers.find(discNumber); auto it = trackContainers.find(discNumber);
@@ -178,9 +178,13 @@ Release::refreshView()
} }
Wt::WTemplate* disc {rootContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.disc-entry"))}; Wt::WTemplate* disc {rootContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.disc-entry"))};
disc->bindNew<Wt::WText>("disc-title", Wt::WString::tr("Lms.Explore.Release.disc").arg(discNumber));
disc->addFunction("tr", &Wt::WTemplate::Functions::tr); disc->addFunction("tr", &Wt::WTemplate::Functions::tr);
if (discSubtitle.empty())
disc->bindNew<Wt::WText>("disc-title", Wt::WString::tr("Lms.Explore.Release.disc").arg(discNumber));
else
disc->bindString("disc-title", Wt::WString::fromUTF8(discSubtitle), Wt::TextFormat::Plain);
Wt::WContainerWidget* tracksContainer {disc->bindNew<Wt::WContainerWidget>("tracks")}; Wt::WContainerWidget* tracksContainer {disc->bindNew<Wt::WContainerWidget>("tracks")};
trackContainers[discNumber] = tracksContainer; trackContainers[discNumber] = tracksContainer;
@@ -198,7 +202,7 @@ Release::refreshView()
Wt::WContainerWidget* container {rootContainer}; Wt::WContainerWidget* container {rootContainer};
if (isReleaseMultiDisc && discNumber) if (isReleaseMultiDisc && discNumber)
container = getOrAddDiscContainer(*discNumber); container = getOrAddDiscContainer(*discNumber, track->getDiscSubtitle());
Wt::WTemplate* entry {container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry"))}; Wt::WTemplate* entry {container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry"))};
+3
View File
@@ -110,6 +110,9 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file)
if (track->discNumber) if (track->discNumber)
std::cout << "Disc: " << *track->discNumber << std::endl; std::cout << "Disc: " << *track->discNumber << std::endl;
if (!track->discSubtitle.empty())
std::cout << "Disc Subtitle: " << track->discSubtitle << std::endl;
if (track->totalDisc) if (track->totalDisc)
std::cout << "TotalDisc: " << *track->totalDisc << std::endl; std::cout << "TotalDisc: " << *track->totalDisc << std::endl;