Added disc subtitles support. Fixes #56
This commit is contained in:
@@ -15,12 +15,13 @@ A [demo](http://lms.demo.poupon.io) instance is available, with the following li
|
||||
* User management
|
||||
* Recommendation engine
|
||||
* Audio transcode for maximum interoperability and low bandwith requirements
|
||||
* ReplayGain support
|
||||
* Persistent play queue across sessions
|
||||
* Compilation support
|
||||
* Multi-value tags: artists, genres, ...
|
||||
* Custom tags (ex: _mood_, _genre_, _albummood_, _albumgrouping_, ...)
|
||||
* MusicBrainzID support to handle duplicated artist and release names
|
||||
* ReplayGain support
|
||||
* Disc subtitles support
|
||||
* _Systemd_ integration
|
||||
* Subsonic API, with the following additional features:
|
||||
* Playlists
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
namespace Database {
|
||||
|
||||
#define LMS_DATABASE_VERSION 22
|
||||
#define LMS_DATABASE_VERSION 23
|
||||
|
||||
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
|
||||
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
|
||||
{
|
||||
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 setTotalTrack(std::optional<int> totalTrack) { totalTrack ? _totalTrack = *totalTrack : 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 setDuration(std::chrono::milliseconds duration) { _duration = duration; }
|
||||
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> getTotalTrack() const;
|
||||
std::optional<std::size_t> getDiscNumber() const;
|
||||
const std::string& getDiscSubtitle() const { return _discSubtitle; }
|
||||
std::optional<std::size_t> getTotalDisc() const;
|
||||
std::string getName() const { return _name; }
|
||||
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, _trackNumber, "track_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, _totalDisc, "total_disc");
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
@@ -177,6 +180,7 @@ class Track : public Wt::Dbo::Dbo<Track>
|
||||
int _scanVersion {};
|
||||
int _trackNumber {};
|
||||
int _discNumber {};
|
||||
std::string _discSubtitle;
|
||||
int _totalTrack {};
|
||||
int _totalDisc {};
|
||||
std::string _name;
|
||||
|
||||
@@ -211,6 +211,12 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|
||||
{
|
||||
track.musicBrainzTrackID = UUID::fromString(value);
|
||||
}
|
||||
else if (tag == "TSST"
|
||||
|| tag == "DISCSUBTITLE"
|
||||
|| tag == "SETSUBTITLE")
|
||||
{
|
||||
track.discSubtitle = value;
|
||||
}
|
||||
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||
{
|
||||
std::vector<std::string> clusterNames {StringUtils::splitString(value, "/,;")};
|
||||
|
||||
@@ -270,6 +270,8 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
|
||||
track.albumReplayGain = StringUtils::readAs<float>(value);
|
||||
else if (tag == "REPLAYGAIN_TRACK_GAIN")
|
||||
track.trackReplayGain = StringUtils::readAs<float>(value);
|
||||
else if (tag == "DISCSUBTITLE" || tag == "SETSUBTITLE")
|
||||
track.discSubtitle = value;
|
||||
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||
{
|
||||
std::set<std::string> clusterNames;
|
||||
@@ -372,8 +374,12 @@ TagLibParser::parse(const std::filesystem::path& p, bool debug)
|
||||
{
|
||||
if (mp3File->ID3v2Tag())
|
||||
{
|
||||
if (!mp3File->ID3v2Tag()->frameListMap()["APIC"].isEmpty())
|
||||
const auto& frameListMap {mp3File->ID3v2Tag()->frameListMap()};
|
||||
|
||||
if (!frameListMap["APIC"].isEmpty())
|
||||
track.hasCover = true;
|
||||
if (!frameListMap["TSST"].isEmpty())
|
||||
properties.insert("DISCSUBTITLE", frameListMap["TSST"].front()->toString());
|
||||
}
|
||||
|
||||
getAPETags(mp3File->APETag());
|
||||
|
||||
@@ -58,25 +58,26 @@ namespace MetaData
|
||||
{
|
||||
std::vector<Artist> artists;
|
||||
std::vector<Artist> albumArtists;
|
||||
std::string title;
|
||||
std::string title;
|
||||
std::optional<UUID> musicBrainzTrackID;
|
||||
std::optional<UUID> musicBrainzRecordID;
|
||||
std::optional<Album> album;
|
||||
Clusters clusters;
|
||||
std::chrono::milliseconds duration {};
|
||||
std::optional<Album> album;
|
||||
Clusters clusters;
|
||||
std::chrono::milliseconds duration;
|
||||
std::optional<std::size_t> trackNumber;
|
||||
std::optional<std::size_t> totalTrack;
|
||||
std::optional<std::size_t> discNumber;
|
||||
std::optional<std::size_t> totalDisc;
|
||||
std::optional<int> year;
|
||||
std::optional<int> originalYear;
|
||||
bool hasCover {false};
|
||||
bool hasCover {};
|
||||
std::vector<AudioStream> audioStreams;
|
||||
std::optional<UUID> acoustID;
|
||||
std::string copyright;
|
||||
std::string copyrightURL;
|
||||
std::optional<float> trackReplayGain;
|
||||
std::optional<float> albumReplayGain;
|
||||
std::string copyright;
|
||||
std::string copyrightURL;
|
||||
std::optional<float> trackReplayGain;
|
||||
std::optional<float> albumReplayGain;
|
||||
std::string discSubtitle;
|
||||
};
|
||||
|
||||
class IParser
|
||||
|
||||
@@ -738,6 +738,8 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S
|
||||
track.modify()->setDiscNumber(trackInfo->discNumber ? *trackInfo->discNumber : 0);
|
||||
track.modify()->setTotalTrack(trackInfo->totalTrack);
|
||||
track.modify()->setTotalDisc(trackInfo->totalDisc);
|
||||
if (!trackInfo->discSubtitle.empty())
|
||||
track.modify()->setDiscSubtitle(trackInfo->discSubtitle);
|
||||
track.modify()->setYear(trackInfo->year ? *trackInfo->year : 0);
|
||||
track.modify()->setOriginalYear(trackInfo->originalYear ? *trackInfo->originalYear : 0);
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ Release::refreshView()
|
||||
|
||||
// Expect to be call in asc order
|
||||
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);
|
||||
@@ -178,9 +178,13 @@ Release::refreshView()
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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")};
|
||||
trackContainers[discNumber] = tracksContainer;
|
||||
|
||||
@@ -198,7 +202,7 @@ Release::refreshView()
|
||||
|
||||
Wt::WContainerWidget* container {rootContainer};
|
||||
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"))};
|
||||
|
||||
|
||||
@@ -110,6 +110,9 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file)
|
||||
if (track->discNumber)
|
||||
std::cout << "Disc: " << *track->discNumber << std::endl;
|
||||
|
||||
if (!track->discSubtitle.empty())
|
||||
std::cout << "Disc Subtitle: " << track->discSubtitle << std::endl;
|
||||
|
||||
if (track->totalDisc)
|
||||
std::cout << "TotalDisc: " << *track->totalDisc << std::endl;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user