Added a message box to display some track information. closes #276

This commit is contained in:
emeric
2022-11-21 21:04:23 +01:00
parent a2b1199e87
commit 2b7d3bfaf4
35 changed files with 573 additions and 180 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ find_package(Wt REQUIRED COMPONENTS Wt Dbo DboSqlite3 HTTP)
pkg_check_modules(Taglib REQUIRED IMPORTED_TARGET taglib)
pkg_check_modules(Config++ REQUIRED IMPORTED_TARGET libconfig++)
pkg_check_modules(GraphicsMagick++ IMPORTED_TARGET GraphicsMagick++)
pkg_check_modules(LIBAV IMPORTED_TARGET libavutil libavformat)
pkg_check_modules(LIBAV IMPORTED_TARGET libavcodec libavutil libavformat)
find_package(PAM)
find_package(STB)
+4 -4
View File
@@ -44,12 +44,12 @@
</div>
</div>
<div class="col-12">
<label class="form-label" for="${id:tags}">
${tr:Lms.Admin.Database.tags}
<label class="form-label" for="${id:clusters}">
${tr:Lms.clusters}
</label>
${tags class="form-control"}
${clusters class="form-control"}
<div class="invalid-feedback">
${tags-info}
${clusters-info}
</div>
</div>
<div class="col-12">
+34 -8
View File
@@ -7,6 +7,7 @@
<message id="Lms.administration">Administration</message>
<message id="Lms.apply">Apply</message>
<message id="Lms.cancel">Cancel</message>
<message id="Lms.clusters">Tags</message>
<message id="Lms.create">Create</message>
<message id="Lms.delete">Delete</message>
<message id="Lms.discard">Discard</message>
@@ -15,6 +16,7 @@
<message id="Lms.login">Login</message>
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Logout</message>
<message id="Lms.not-a-directory">Not a directory</message>
<message id="Lms.ok">Ok</message>
<message id="Lms.password">Password</message>
<message id="Lms.password-bad-login-combination">Bad login / password combination</message>
<message id="Lms.password-client-throttled">Login throttled, please try again later</message>
@@ -58,7 +60,6 @@
<message id="Lms.Admin.Database.scan-launched">Scan launched!</message>
<message id="Lms.Admin.Database.scan-options">Scan options</message>
<message id="Lms.Admin.Database.settings-saved">New settings saved!</message>
<message id="Lms.Admin.Database.tags">Tags</message>
<message id="Lms.Admin.Database.update-period">Update period</message>
<message id="Lms.Admin.Database.update-start-time">Update start time</message>
<message id="Lms.Admin.Database.weekly">Weekly</message>
@@ -119,7 +120,10 @@
<message id="Lms.Explore.add-filter">Add filter</message>
<message id="Lms.Explore.all">All</message>
<message id="Lms.Explore.artists">Artists</message>
<message id="Lms.Explore.bitrate">Bitrate</message>
<message id="Lms.Explore.codec">Codec</message>
<message id="Lms.Explore.download">Download</message>
<message id="Lms.Explore.duration">Duration</message>
<message id="Lms.Explore.filter-added">Filter added</message>
<message id="Lms.Explore.filters">Filters</message>
<message id="Lms.Explore.most-played">Most played</message>
@@ -136,6 +140,7 @@
<message id="Lms.Explore.search">Search</message>
<message id="Lms.Explore.star">Star</message>
<message id="Lms.Explore.starred">Starred</message>
<message id="Lms.Explore.track-info">Track information</message>
<message id="Lms.Explore.tracklists">Playlists</message>
<message id="Lms.Explore.tracks">Tracks</message>
<message id="Lms.Explore.type">Type</message>
@@ -149,13 +154,34 @@
<!--Explore:Artists-->
<message id="Lms.Explore.Artists.linktype-all">All artists</message>
<message id="Lms.Explore.Artists.linktype-artist">Track artists</message>
<message id="Lms.Explore.Artists.linktype-composer">Composers</message>
<message id="Lms.Explore.Artists.linktype-lyricist">Lyricists</message>
<message id="Lms.Explore.Artists.linktype-mixer">Mixers</message>
<message id="Lms.Explore.Artists.linktype-producer">Producers</message>
<message id="Lms.Explore.Artists.linktype-releaseartist">Album artists</message>
<message id="Lms.Explore.Artists.linktype-remixer">Remixers</message>
<message id="Lms.Explore.Artists.linktype-artist">
<plural case="0">Track artist</plural>
<plural case="1">Track artists</plural>
</message>
<message id="Lms.Explore.Artists.linktype-composer">
<plural case="0">Composer</plural>
<plural case="1">Composers</plural>
</message>
<message id="Lms.Explore.Artists.linktype-lyricist">
<plural case="0">Lyricist</plural>
<plural case="1">Lyricists</plural>
</message>
<message id="Lms.Explore.Artists.linktype-mixer">
<plural case="0">Mixer</plural>
<plural case="1">Mixers</plural>
</message>
<message id="Lms.Explore.Artists.linktype-producer">
<plural case="0">Producer</plural>
<plural case="1">Producers</plural>
</message>
<message id="Lms.Explore.Artists.linktype-releaseartist">
<plural case="0">Album artists</plural>
<plural case="1">Album artist</plural>
</message>
<message id="Lms.Explore.Artists.linktype-remixer">
<plural case="0">Remixers</plural>
<plural case="1">Remixer</plural>
</message>
<!--Explore:Release-->
<message id="Lms.Explore.Release.similar-releases">Similar albums</message>
+34 -8
View File
@@ -7,6 +7,7 @@
<message id="Lms.administration">Administration</message>
<message id="Lms.apply">Appliquer</message>
<message id="Lms.cancel">Annuler</message>
<message id="Lms.clusters">Tags</message>
<message id="Lms.create">Créer</message>
<message id="Lms.delete">Supprimer</message>
<message id="Lms.discard">Annuler</message>
@@ -15,6 +16,7 @@
<message id="Lms.login">Login</message>
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Quitter</message>
<message id="Lms.not-a-directory">N'est pas un répertoire</message>
<message id="Lms.ok">Ok</message>
<message id="Lms.password">Mot de passe</message>
<message id="Lms.password-bad-login-combination">Mauvaise combinaison login / mot de passe</message>
<message id="Lms.password-client-throttled">Trop de tentatives de connexion, veuillez réessayer plus tard</message>
@@ -58,7 +60,6 @@
<message id="Lms.Admin.Database.scan-launched">Scan lancé !</message>
<message id="Lms.Admin.Database.scan-options">Options </message>
<message id="Lms.Admin.Database.settings-saved">Nouveaux paramètres sauvegardés !</message>
<message id="Lms.Admin.Database.tags">Tags</message>
<message id="Lms.Admin.Database.update-period">Périodicité des mises à jour</message>
<message id="Lms.Admin.Database.update-start-time">Heure de départ de la mise à jour</message>
<message id="Lms.Admin.Database.weekly">Toutes les semaines</message>
@@ -119,7 +120,10 @@
<message id="Lms.Explore.add-filter">Ajouter filtre</message>
<message id="Lms.Explore.all">Tous</message>
<message id="Lms.Explore.artists">Artistes</message>
<message id="Lms.Explore.bitrate">Bitrate</message>
<message id="Lms.Explore.codec">Codec</message>
<message id="Lms.Explore.download">Télécharger</message>
<message id="Lms.Explore.duration">Durée</message>
<message id="Lms.Explore.filter-added">Filtre ajouté</message>
<message id="Lms.Explore.filters">Filtres</message>
<message id="Lms.Explore.most-played">Plus joués</message>
@@ -136,6 +140,7 @@
<message id="Lms.Explore.search">Rechercher</message>
<message id="Lms.Explore.star">Ajouter aux favoris</message>
<message id="Lms.Explore.starred">Favoris</message>
<message id="Lms.Explore.track-info">Informations sur la piste</message>
<message id="Lms.Explore.tracklists">Playlists</message>
<message id="Lms.Explore.tracks">Pistes</message>
<message id="Lms.Explore.type">Type</message>
@@ -149,13 +154,34 @@
<!--Explore:Artists-->
<message id="Lms.Explore.Artists.linktype-all">Tous les artistes</message>
<message id="Lms.Explore.Artists.linktype-artist">Artistes de piste</message>
<message id="Lms.Explore.Artists.linktype-composer">Compositeurs</message>
<message id="Lms.Explore.Artists.linktype-lyricist">Paroliers</message>
<message id="Lms.Explore.Artists.linktype-mixer">Mixers</message>
<message id="Lms.Explore.Artists.linktype-producer">Producteurs</message>
<message id="Lms.Explore.Artists.linktype-releaseartist">Artistes d'album</message>
<message id="Lms.Explore.Artists.linktype-remixer">Remixers</message>
<message id="Lms.Explore.Artists.linktype-artist">
<plural case="0">Artiste de piste</plural>
<plural case="1">Artistes de piste</plural>
</message>
<message id="Lms.Explore.Artists.linktype-composer">
<plural case="0">Compositeur</plural>
<plural case="1">Compositeurs</plural>
</message>
<message id="Lms.Explore.Artists.linktype-lyricist">
<plural case="0">Parolier</plural>
<plural case="1">Paroliers</plural>
</message>
<message id="Lms.Explore.Artists.linktype-mixer">
<plural case="0">Mixeur</plural>
<plural case="1">Mixeurs</plural>
</message>
<message id="Lms.Explore.Artists.linktype-producer">
<plural case="0">Producteur</plural>
<plural case="1">Producteurs</plural>
</message>
<message id="Lms.Explore.Artists.linktype-releaseartist">
<plural case="0">Artiste d'album</plural>
<plural case="1">Artistes d'album</plural>
</message>
<message id="Lms.Explore.Artists.linktype-remixer">
<plural case="0">Remixeur</plural>
<plural case="1">Remixeurs</plural>
</message>
<!--Explore:Release-->
<message id="Lms.Explore.Release.similar-releases">Albums similaires</message>
+35 -8
View File
@@ -7,6 +7,7 @@
<message id="Lms.administration">Amministrazione</message>
<message id="Lms.apply">Applica</message>
<message id="Lms.cancel">Annulla</message>
<message id="Lms.clusters">Tags</message>
<message id="Lms.create">Crea</message>
<message id="Lms.del">Elimina</message>
<message id="Lms.discard">Annulla</message>
@@ -15,6 +16,7 @@
<message id="Lms.login">Login</message>
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Logout</message>
<message id="Lms.not-a-directory">Non è una directory</message>
<message id="Lms.password">Password</message>
<message id="Lms.password-bad-login-combination">Errata combinazione di Login / Password </message>
<message id="Lms.password-client-throttled">Superati i tentativi di accesso, riprova più tardi</message>
@@ -58,7 +60,6 @@
<message id="Lms.Admin.Database.scan-launched">Scansione avviata!</message>
<message id="Lms.Admin.Database.scan-options">Impostazioni scansione</message>
<message id="Lms.Admin.Database.settings-saved">Impostazioni salvate!</message>
<message id="Lms.Admin.Database.tags">Tags</message>
<message id="Lms.Admin.Database.update-period">Frequenza di aggiornamento</message>
<message id="Lms.Admin.Database.update-start-time">Orario di aggiornamento</message>
<message id="Lms.Admin.Database.weekly">Settimanale</message>
@@ -119,7 +120,10 @@
<message id="Lms.Explore.add-filter">Aggiungi filtro</message>
<message id="Lms.Explore.all">Tutti</message>
<message id="Lms.Explore.artists">Artisti</message>
<message id="Lms.Explore.download">Download</message>
<message id="Lms.Explore.filter-added">Filtro aggiunto</message>
<message id="Lms.Explore.filters">Filtri</message>
<message id="Lms.Explore.most-played">Più ascoltati</message>
@@ -136,6 +140,8 @@
<message id="Lms.Explore.search">Ricerca</message>
<message id="Lms.Explore.star">Aggiungi ai preferiti</message>
<message id="Lms.Explore.starred">Preferiti</message>
<message id="Lms.Explore.tracks">Tracce</message>
<message id="Lms.Explore.type">Tipo</message>
<message id="Lms.Explore.unstar">Rimuovi dai preferiti</message>
@@ -148,13 +154,34 @@
<!--Explore:Artists-->
<message id="Lms.Explore.Artists.linktype-all">Tutti gli artisti</message>
<message id="Lms.Explore.Artists.linktype-artist">Artisti tracce</message>
<message id="Lms.Explore.Artists.linktype-composer">Compositori</message>
<message id="Lms.Explore.Artists.linktype-lyricist">Scrittori</message>
<message id="Lms.Explore.Artists.linktype-mixer">Mixers</message>
<message id="Lms.Explore.Artists.linktype-producer">Produttori</message>
<message id="Lms.Explore.Artists.linktype-releaseartist">Artisti album</message>
<message id="Lms.Explore.Artists.linktype-remixer">Remixers</message>
<!--Explore:Release-->
<message id="Lms.Explore.Release.similar-releases">Album simili</message>
+42 -10
View File
@@ -7,14 +7,16 @@
<message id="Lms.administration">管理</message>
<message id="Lms.apply">应用</message>
<message id="Lms.cancel">取消</message>
<message id="Lms.clusters">标签</message>
<message id="Lms.create">新建</message>
<message id="Lms.del">删除</message>
<message id="Lms.delete">删除</message>
<message id="Lms.discard">撤销</message>
<message id="Lms.edit">编辑</message>
<message id="Lms.loading">加载中...</message>
<message id="Lms.login">登陆</message>
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> 注销</message>
<message id="Lms.not-a-directory">不是一个目录</message>
<message id="Lms.password">密码</message>
<message id="Lms.password-bad-login-combination">用户名或密码无效</message>
<message id="Lms.password-client-throttled">登录已被限制,请稍后再试</message>
@@ -26,7 +28,10 @@
<message id="Lms.passwords-dont-match">密码不匹配</message>
<message id="Lms.quit-other-session">另一个会话已打开,重新打开这一个?</message>
<message id="Lms.save">保存</message>
<message id="Lms.track-count">{1} 个音轨</message>
<message id="Lms.user">用户</message>
<!--Errors-->
@@ -55,7 +60,6 @@
<message id="Lms.Admin.Database.scan-launched">扫描已完成!</message>
<message id="Lms.Admin.Database.scan-options">扫描选项</message>
<message id="Lms.Admin.Database.settings-saved">新设置已保存!</message>
<message id="Lms.Admin.Database.tags">标签</message>
<message id="Lms.Admin.Database.update-period">更新周期</message>
<message id="Lms.Admin.Database.update-start-time">更新开始时间</message>
<message id="Lms.Admin.Database.weekly">每周</message>
@@ -116,7 +120,10 @@
<message id="Lms.Explore.add-filter">添加过滤器</message>
<message id="Lms.Explore.all">全部</message>
<message id="Lms.Explore.artists">歌手</message>
<message id="Lms.Explore.download">下载</message>
<message id="Lms.Explore.filter-added">过滤器已添加</message>
<message id="Lms.Explore.filters">过滤器</message>
<message id="Lms.Explore.most-played">播放最多</message>
@@ -133,6 +140,7 @@
<message id="Lms.Explore.search">搜索</message>
<message id="Lms.Explore.star">收藏</message>
<message id="Lms.Explore.starred">已收藏</message>
<message id="Lms.Explore.tracklists">播放列表</message>
<message id="Lms.Explore.tracks">音轨</message>
<message id="Lms.Explore.type">类型</message>
@@ -147,12 +155,33 @@
<!--Explore:Artists-->
<message id="Lms.Explore.Artists.linktype-all">所有歌手</message>
<message id="Lms.Explore.Artists.linktype-artist">音轨歌手</message>
<message id="Lms.Explore.Artists.linktype-composer">作曲</message>
<message id="Lms.Explore.Artists.linktype-lyricist">作词</message>
<message id="Lms.Explore.Artists.linktype-mixer">混响</message>
<message id="Lms.Explore.Artists.linktype-producer">制作人</message>
<message id="Lms.Explore.Artists.linktype-releaseartist">专辑歌手</message>
<message id="Lms.Explore.Artists.linktype-remixer">混音师</message>
<!--Explore:Release-->
<message id="Lms.Explore.Release.similar-releases">相似专辑</message>
@@ -171,7 +200,10 @@
<!--Playqueue-->
<message id="Lms.PlayQueue.clear">清除</message>
<message id="Lms.PlayQueue.create-tracklist">创建新播放列表</message>
<message id="Lms.PlayQueue.nb-tracks-added">已添加 {1} 音轨</message>
<message id="Lms.PlayQueue.playqueue">播放队列</message>
<message id="Lms.PlayQueue.playqueue-full">播放队列已满!</message>
<message id="Lms.PlayQueue.radio-mode">广播模式</message>
+1
View File
@@ -77,6 +77,7 @@
<li>${play-last class="dropdown-item"}</li>
<li>${star class="dropdown-item"}</li>
<li>${download class="dropdown-item"}</li>
<li>${track-info class="dropdown-item"}</li>
</ul>
</div>
</div>
+1 -1
View File
@@ -30,7 +30,7 @@
<div class="col-lg-4">
<label class="form-label" for="${id:transcode-bitrate}">
${tr:Lms.Settings.transcode-bitrate}
</label>
; </label>
<div class="input-group mb-3">
${transcode-bitrate class="form-control"}
<span class="input-group-text">kbps</span>
+71
View File
@@ -57,6 +57,7 @@
<li>${play-last class="dropdown-item"}</li>
<li>${star class="dropdown-item"}</li>
<li>${download class="dropdown-item"}</li>
<li>${track-info class="dropdown-item"}</li>
</ul>
</div>
</div>
@@ -67,4 +68,74 @@
${artist class="text-decoration-none link-secondary"}
</message>
<message id="Lms.Explore.Tracks.template.track-info">
<div class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${tr:Lms.Explore.track-info}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="container-fluid d-grid gap-1">
${<if-has-clusters>}
<div class="row align-items-center">
<div class="col-4">
${tr:Lms.clusters}
</div>
<div class="col-8">
${clusters}
</div>
</div>
${</if-has-clusters>}
${artist-table class="d-grid gap-1"}
<div class="row align-items-center">
<div class="col-4">
${tr:Lms.Explore.duration}
</div>
<div class="col-8">
${duration}
</div>
</div>
${<if-has-codec>}
<div class="row align-items-center">
<div class="col-4">
${tr:Lms.Explore.codec}
</div>
<div class="col-8">
${codec}
</div>
</div>
${</if-has-codec>}
${<if-has-bitrate>}
<div class="row align-items-center">
<div class="col-4">
${tr:Lms.Explore.bitrate}
</div>
<div class="col-8">
${bitrate}
</div>
</div>
${</if-has-bitrate>}
</div>
</div>
<div class="modal-footer">
${ok-btn class="btn btn-primary"}
</div>
</div>
</div>
</div>
</message>
<message id="Lms.Explore.Tracks.template.track-info.artists">
<div class="row align-items-center">
<div class="col-4">
${type}
</div>
<div class="col-8">
${artist-container data-bs-dismiss="modal"}
</div>
</div>
</message>
</messages>
+52 -23
View File
@@ -23,6 +23,7 @@ extern "C"
{
#define __STDC_CONSTANT_MACROS
#include <libavcodec/avcodec.h>
#include <libavcodec/codec_par.h>
#include <libavformat/avformat.h>
#include <libavutil/error.h>
}
@@ -38,7 +39,7 @@ static std::string averror_to_string(int error)
{
std::array<char, 128> buf = {0};
if (av_strerror(error, buf.data(), buf.size()) == 0)
if (::av_strerror(error, buf.data(), buf.size()) == 0)
return &buf[0];
else
return "Unknown error";
@@ -104,7 +105,7 @@ getMetaDataFromDictionnary(AVDictionary* dictionnary, AudioFile::MetadataMap& re
return;
AVDictionaryEntry *tag = NULL;
while ((tag = av_dict_get(dictionnary, "", tag, AV_DICT_IGNORE_SUFFIX)))
while ((tag = ::av_dict_get(dictionnary, "", tag, AV_DICT_IGNORE_SUFFIX)))
{
res[StringUtils::stringToUpper(tag->key)] = tag->value;
}
@@ -140,31 +141,18 @@ AudioFile::getStreamInfo() const
for (std::size_t i {}; i < _context->nb_streams; ++i)
{
AVStream* avstream { _context->streams[i]};
// Skip attached pics
if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC)
continue;
if (!avstream->codecpar)
{
LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codecpar is set";
continue;
}
if (avstream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
continue;
res.push_back( {i, static_cast<std::size_t>(avstream->codecpar->bit_rate)} );
std::optional<StreamInfo> streamInfo {getStreamInfo(i)};
if (streamInfo)
res.emplace_back(std::move(*streamInfo));
}
return res;
}
std::optional<std::size_t>
AudioFile::getBestStream() const
AudioFile::getBestStreamIndex() const
{
int res = av_find_best_stream(_context,
int res = ::av_find_best_stream(_context,
AVMEDIA_TYPE_AUDIO,
-1, // Auto
-1, // Auto
@@ -177,6 +165,18 @@ AudioFile::getBestStream() const
return res;
}
std::optional<StreamInfo>
AudioFile::getBestStreamInfo() const
{
std::optional<StreamInfo> res;
std::optional<std::size_t> bestStreamIndex {getBestStreamIndex()};
if (bestStreamIndex)
res = getStreamInfo(*bestStreamIndex);
return res;
}
bool
AudioFile::hasAttachedPictures() const
{
@@ -238,10 +238,39 @@ AudioFile::visitAttachedPictures(std::function<void(const Picture&)> func) const
}
}
std::optional<AudioFileFormat>
guessMediaFileFormat(const std::filesystem::path& file)
std::optional<StreamInfo>
AudioFile::getStreamInfo(std::size_t streamIndex) const
{
const AVOutputFormat* format {av_guess_format(NULL,file.string().c_str(),NULL)};
std::optional<StreamInfo> res;
AVStream* avstream { _context->streams[streamIndex]};
assert(avstream);
if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC)
return res;
if (!avstream->codecpar)
{
LMS_LOG(AV, ERROR) << "Skipping stream " << streamIndex << " since no codecpar is set";
return res;
}
if (avstream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
return res;
res.emplace();
res->index = streamIndex;
res->bitrate = static_cast<std::size_t>(avstream->codecpar->bit_rate);
res->codec = ::avcodec_get_name(avstream->codecpar->codec_id);
assert(!res->codec.empty());
return res;
}
std::optional<AudioFileFormat>
guessAudioFileFormat(const std::filesystem::path& file)
{
const AVOutputFormat* format {::av_guess_format(NULL, file.string().c_str(), NULL)};
if (!format || !format->name)
return {};
+4 -1
View File
@@ -43,11 +43,14 @@ namespace Av
std::chrono::milliseconds getDuration() const override;
MetadataMap getMetaData() const override;
std::vector<StreamInfo> getStreamInfo() const override;
std::optional<std::size_t> getBestStream() const override;
std::optional<StreamInfo> getBestStreamInfo() const override;
std::optional<std::size_t> getBestStreamIndex() const override;
bool hasAttachedPictures() const override;
void visitAttachedPictures(std::function<void(const Picture&)> func) const override;
private:
std::optional<StreamInfo> getStreamInfo(std::size_t streamIndex) const;
const std::filesystem::path _p;
AVFormatContext* _context {};
};
+6 -4
View File
@@ -37,13 +37,14 @@ namespace Av
{
std::string mimeType;
const std::byte* data {};
std::size_t dataSize;
std::size_t dataSize {};
};
struct StreamInfo
{
size_t id;
std::size_t bitrate;
size_t index {};
std::size_t bitrate {};
std::string codec;
};
class IAudioFile
@@ -57,7 +58,8 @@ namespace Av
virtual std::chrono::milliseconds getDuration() const = 0;
virtual MetadataMap getMetaData() const = 0;
virtual std::vector<StreamInfo> getStreamInfo() const = 0;
virtual std::optional<std::size_t> getBestStream() const = 0; // none if failure/unknown
virtual std::optional<StreamInfo> getBestStreamInfo() const = 0; // none if failure/unknown
virtual std::optional<std::size_t> getBestStreamIndex() const = 0; // none if failure/unknown
virtual bool hasAttachedPictures() const = 0;
virtual void visitAttachedPictures(std::function<void(const Picture&)> func) const = 0;
};
+9 -1
View File
@@ -94,7 +94,10 @@ createQuery(Session& session, const Artist::FindParameters& params)
session.checkSharedLocked();
auto query {session.getDboSession().query<ArtistId>("SELECT DISTINCT a.id FROM artist a")};
if (params.sortMethod == ArtistSortMethod::LastWritten || params.writtenAfter.isValid() || params.linkType)
if (params.sortMethod == ArtistSortMethod::LastWritten
|| params.writtenAfter.isValid()
|| params.linkType
|| params.track.isValid())
{
query.join("track t ON t.id = t_a_l.track_id");
query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id");
@@ -157,6 +160,11 @@ createQuery(Session& session, const Artist::FindParameters& params)
query.where(oss.str());
}
if (params.track.isValid())
{
query.where("t.id = ?").bind(params.track);
}
switch (params.sortMethod)
{
case ArtistSortMethod::None:
@@ -33,6 +33,7 @@
#include "services/database/ReleaseId.hpp"
#include "services/database/Types.hpp"
#include "services/database/UserId.hpp"
#include "services/database/TrackId.hpp"
#include "utils/EnumSet.hpp"
#include "utils/UUID.hpp"
@@ -61,6 +62,7 @@ class Artist : public Object<Artist, ArtistId>
Wt::WDateTime writtenAfter;
UserId starringUser; // only artists starred by this user
std::optional<Scrobbler> scrobbler; // and for this scrobbler
TrackId track; // artists involved in this track
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
@@ -69,6 +71,7 @@ class Artist : public Object<Artist, ArtistId>
FindParameters& setRange(Range _range) {range = _range; return *this; }
FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; }
FindParameters& setStarringUser(UserId _user, Scrobbler _scrobbler) { starringUser = _user; scrobbler = _scrobbler; return *this; }
FindParameters& setTrack(TrackId _track) { track = _track; return *this; }
};
Artist() = default;
+10 -4
View File
@@ -92,21 +92,27 @@ TEST_F(DatabaseFixture, Artist_singleTrack)
}
{
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createSharedTransaction()};
auto tracks {Track::find(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtist"))};
ASSERT_EQ(tracks.results.size(), 1);
EXPECT_EQ(tracks.results.front(), track.getId());
}
{
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createSharedTransaction()};
auto tracks {Track::find(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtistFoo"))};
EXPECT_EQ(tracks.results.size(), 0);
}
{
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createSharedTransaction()};
auto tracks {Track::find(session, Track::FindParameters{}.setName("MyTrackNameFoo").setArtistName("MyArtist"))};
EXPECT_EQ(tracks.results.size(), 0);
}
{
auto transaction {session.createSharedTransaction()};
auto artists {Artist::find(session, Artist::FindParameters{}.setTrack(track->getId()))};
ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId());
}
}
TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
@@ -198,7 +204,7 @@ TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists)
}
{
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createSharedTransaction()};
auto tracks {Track::find(session, Track::FindParameters {}.setArtist(artist1->getId()))};
ASSERT_EQ(tracks.results.size(), 1);
-65
View File
@@ -278,71 +278,6 @@ LmsApplication::finalize()
preQuit().emit();
}
Wt::WLink
LmsApplication::createArtistLink(Database::Artist::pointer artist)
{
if (const auto mbid {artist->getMBID()})
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/mbid/" + std::string {mbid->getAsString()}};
else
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString()};
}
std::unique_ptr<Wt::WAnchor>
LmsApplication::createArtistAnchor(Database::Artist::pointer artist, bool addText)
{
auto res = std::make_unique<Wt::WAnchor>(createArtistLink(artist));
if (addText)
{
res->setTextFormat(Wt::TextFormat::Plain);
res->setText(Wt::WString::fromUTF8(artist->getName()));
res->setToolTip(Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
}
return res;
}
Wt::WLink
LmsApplication::createReleaseLink(Database::Release::pointer release)
{
if (const auto mbid {release->getMBID()})
return Wt::WLink {Wt::LinkType::InternalPath, "/release/mbid/" + std::string {mbid->getAsString()}};
else
return Wt::WLink {Wt::LinkType::InternalPath, "/release/" + release->getId().toString()};
}
std::unique_ptr<Wt::WAnchor>
LmsApplication::createReleaseAnchor(Database::Release::pointer release, bool addText)
{
auto res = std::make_unique<Wt::WAnchor>(createReleaseLink(release));
if (addText)
{
res->setTextFormat(Wt::TextFormat::Plain);
res->setText(Wt::WString::fromUTF8(release->getName()));
res->setToolTip(Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
}
return res;
}
std::unique_ptr<Wt::WAnchor>
LmsApplication::createTrackListAnchor(Database::TrackList::pointer trackList, bool addText)
{
Wt::WLink link {Wt::LinkType::InternalPath, "/tracklist/" + trackList->getId().toString()};
auto res {std::make_unique<Wt::WAnchor>(link)};
if (addText)
{
const Wt::WString name {Wt::WString::fromUTF8(std::string {trackList->getName()})};
res->setTextFormat(Wt::TextFormat::Plain);
res->setText(name);
res->setToolTip(name, Wt::TextFormat::Plain);
}
return res;
}
void
LmsApplication::handleException(LmsApplicationException& e)
{
-10
View File
@@ -31,12 +31,8 @@
namespace Database
{
class Artist;
class Cluster;
class Db;
class Release;
class Session;
class TrackList;
class User;
}
@@ -80,12 +76,6 @@ class LmsApplication : public Wt::WApplication
// Used to classify the message sent to the user
void notifyMsg(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration = std::chrono::milliseconds {5000});
static Wt::WLink createArtistLink(Database::ObjectPtr<Database::Artist> artist);
static std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::ObjectPtr<Database::Artist> artist, bool addText = true);
static Wt::WLink createReleaseLink(Database::ObjectPtr<Database::Release> release);
static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Database::ObjectPtr<Database::Release> release, bool addText = true);
static std::unique_ptr<Wt::WAnchor> createTrackListAnchor(Database::ObjectPtr<Database::TrackList> trackList, bool addText = true);
MediaPlayer& getMediaPlayer() const { return *_mediaPlayer; }
PlayQueue& getPlayQueue() const { return *_playQueue; }
ModalManager& getModalManager() const { return *_modalManager; }
+4 -3
View File
@@ -42,6 +42,7 @@
#include "utils/Utils.hpp"
#include "LmsApplication.hpp"
#include "Utils.hpp"
namespace UserInterface {
@@ -155,7 +156,7 @@ replayGainPreAmpGainFromString(const std::string& str)
if (!value)
return std::nullopt;
return Utils::clamp(*value, (double)MediaPlayer::Settings::ReplayGain::minPreAmpGain, (double)MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
return ::Utils::clamp(*value, (double)MediaPlayer::Settings::ReplayGain::minPreAmpGain, (double)MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
}
static MediaPlayer::Settings settingsfromJSString(const std::string& strSettings)
@@ -281,7 +282,7 @@ MediaPlayer::loadTrack(Database::TrackId trackId, bool play, float replayGain)
{
_artist->setTextFormat(Wt::TextFormat::Plain);
_artist->setText(Wt::WString::fromUTF8(artists.front()->getName()));
_artist->setLink(LmsApp->createArtistLink(artists.front()));
_artist->setLink(Utils::createArtistLink(artists.front()));
}
else
{
@@ -293,7 +294,7 @@ MediaPlayer::loadTrack(Database::TrackId trackId, bool play, float replayGain)
{
_release->setTextFormat(Wt::TextFormat::Plain);
_release->setText(Wt::WString::fromUTF8(track->getRelease()->getName()));
_release->setLink(LmsApp->createReleaseLink(track->getRelease()));
_release->setLink(Utils::createReleaseLink(track->getRelease()));
}
else
{
+2 -2
View File
@@ -500,9 +500,9 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
if (release)
{
entry->setCondition("if-has-release", true);
entry->bindWidget("release", LmsApplication::createReleaseAnchor(release));
entry->bindWidget("release", Utils::createReleaseAnchor(release));
{
Wt::WAnchor* anchor {entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false))};
Wt::WAnchor* anchor {entry->bindWidget("cover", Utils::createReleaseAnchor(release, false))};
auto cover {Utils::createCover(release->getId(), CoverResource::Size::Small)};
cover->addStyleClass("Lms-cover-track Lms-cover-anchor"); // HACK
anchor->setImage(std::move(cover));
+126
View File
@@ -22,10 +22,17 @@
#include <iomanip>
#include <sstream>
#include <Wt/WAnchor.h>
#include <Wt/WText.h>
#include "services/database/Artist.hpp"
#include "services/database/Cluster.hpp"
#include "services/database/Release.hpp"
#include "services/database/ScanSettings.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "services/database/TrackList.hpp"
#include "explore/Filters.hpp"
#include "LmsApplication.hpp"
namespace UserInterface::Utils
@@ -112,4 +119,123 @@ namespace UserInterface::Utils
return res;
}
std::unique_ptr<Wt::WContainerWidget>
createArtistContainer(const std::vector<Database::ArtistId>& artistIds)
{
using namespace Database;
std::unique_ptr<Wt::WContainerWidget> artistContainer {std::make_unique<Wt::WContainerWidget>()};
bool firstArtist {true};
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
for (const ArtistId artistId : artistIds)
{
const Artist::pointer artist {Artist::find(LmsApp->getDbSession(), artistId)};
if (!artist)
continue;
if (!firstArtist)
artistContainer->addNew<Wt::WText>(" · ");
auto anchor {createArtistAnchor(artist)};
anchor->addStyleClass("link-success text-decoration-none"); // hack
artistContainer->addWidget(std::move(anchor));
firstArtist = false;
}
return artistContainer;
}
Wt::WLink
createArtistLink(Database::Artist::pointer artist)
{
if (const auto mbid {artist->getMBID()})
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/mbid/" + std::string {mbid->getAsString()}};
else
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString()};
}
std::unique_ptr<Wt::WAnchor>
createArtistAnchor(Database::Artist::pointer artist, bool setText)
{
auto res = std::make_unique<Wt::WAnchor>(createArtistLink(artist));
if (setText)
{
res->setTextFormat(Wt::TextFormat::Plain);
res->setText(Wt::WString::fromUTF8(artist->getName()));
res->setToolTip(Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
}
return res;
}
Wt::WLink
createReleaseLink(Database::Release::pointer release)
{
if (const auto mbid {release->getMBID()})
return Wt::WLink {Wt::LinkType::InternalPath, "/release/mbid/" + std::string {mbid->getAsString()}};
else
return Wt::WLink {Wt::LinkType::InternalPath, "/release/" + release->getId().toString()};
}
std::unique_ptr<Wt::WAnchor>
createReleaseAnchor(Database::Release::pointer release, bool setText)
{
auto res = std::make_unique<Wt::WAnchor>(createReleaseLink(release));
if (setText)
{
res->setTextFormat(Wt::TextFormat::Plain);
res->setText(Wt::WString::fromUTF8(release->getName()));
res->setToolTip(Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
}
return res;
}
std::unique_ptr<Wt::WAnchor>
createTrackListAnchor(Database::TrackList::pointer trackList, bool setText)
{
Wt::WLink link {Wt::LinkType::InternalPath, "/tracklist/" + trackList->getId().toString()};
auto res {std::make_unique<Wt::WAnchor>(link)};
if (setText)
{
const Wt::WString name {Wt::WString::fromUTF8(std::string {trackList->getName()})};
res->setTextFormat(Wt::TextFormat::Plain);
res->setText(name);
res->setToolTip(name, Wt::TextFormat::Plain);
}
return res;
}
std::unique_ptr<Wt::WContainerWidget>
createClustersForTrack(Database::Track::pointer track, Filters& filters)
{
using namespace Database;
std::unique_ptr<Wt::WContainerWidget> clusterContainer {std::make_unique<Wt::WContainerWidget>()};
const auto clusterTypes {ScanSettings::get(LmsApp->getDbSession())->getClusterTypes()};
const auto clusterGroups {track->getClusterGroups(clusterTypes, 3)};
for (const auto& clusters : clusterGroups)
{
for (const Cluster::pointer& cluster : clusters)
{
const ClusterId clusterId {cluster->getId()};
Wt::WInteractWidget* entry {clusterContainer->addWidget(Utils::createCluster(clusterId))};
entry->clicked().connect([&filters, clusterId]
{
filters.add(clusterId);
});
}
}
return clusterContainer;
}
}
+28 -2
View File
@@ -22,22 +22,48 @@
#include <chrono>
#include <string>
#include <Wt/WContainerWidget.h>
#include <Wt/WImage.h>
#include <Wt/WInteractWidget.h>
#include "services/database/ArtistId.hpp"
#include "services/database/ClusterId.hpp"
#include "services/database/Object.hpp"
#include "services/database/ReleaseId.hpp"
#include "services/database/TrackId.hpp"
#include "resource/CoverResource.hpp"
namespace Database
{
class Artist;
class Cluster;
class Release;
class Track;
class TrackList;
}
namespace UserInterface
{
class Filters;
}
namespace UserInterface::Utils
{
std::string
durationToString(std::chrono::milliseconds msDuration);
std::string durationToString(std::chrono::milliseconds msDuration);
std::unique_ptr<Wt::WImage> createCover(Database::ReleaseId releaseId, CoverResource::Size size);
std::unique_ptr<Wt::WImage> createCover(Database::TrackId trackId, CoverResource::Size size);
std::unique_ptr<Wt::WInteractWidget> createCluster(Database::ClusterId clusterId, bool canDelete = false);
std::unique_ptr<Wt::WContainerWidget> createArtistContainer(const std::vector<Database::ArtistId>& artists);
Wt::WLink createArtistLink(Database::ObjectPtr<Database::Artist> artist);
std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::ObjectPtr<Database::Artist> artist, bool setText = true);
Wt::WLink createReleaseLink(Database::ObjectPtr<Database::Release> release);
std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Database::ObjectPtr<Database::Release> release, bool setText = true);
std::unique_ptr<Wt::WAnchor> createTrackListAnchor(Database::ObjectPtr<Database::TrackList> trackList, bool setText = true);
std::unique_ptr<Wt::WContainerWidget> createClustersForTrack(Database::ObjectPtr<Database::Track> track, Filters& filters);
}
+6 -6
View File
@@ -53,7 +53,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
static inline constexpr Field UpdatePeriodField {"update-period"};
static inline constexpr Field UpdateStartTimeField {"update-start-time"};
static inline constexpr Field RecommendationEngineTypeField {"recommendation-engine-type"};
static inline constexpr Field TagsField {"tags"};
static inline constexpr Field ClustersField {"clusters"};
using UpdatePeriodModel = ValueStringModel<ScanSettings::UpdatePeriod>;
@@ -65,7 +65,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
addField(UpdatePeriodField);
addField(UpdateStartTimeField);
addField(RecommendationEngineTypeField);
addField(TagsField);
addField(ClustersField);
auto dirValidator {createDirectoryValidator()};
dirValidator->setMandatory(true);
@@ -114,7 +114,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
{
std::vector<std::string> names;
std::transform(clusterTypes.begin(), clusterTypes.end(), std::back_inserter(names), [](auto clusterType) { return clusterType->getName(); });
setValue(TagsField, StringUtils::joinStrings(names, " "));
setValue(ClustersField, StringUtils::joinStrings(names, " "));
}
}
@@ -138,7 +138,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
if (recommendationEngineTypeRow)
scanSettings.modify()->setRecommendationEngineType(_recommendationEngineTypeModel->getValue(*recommendationEngineTypeRow));
auto clusterTypes {StringUtils::splitStringCopy(valueText(TagsField).toUTF8(), " ")};
auto clusterTypes {StringUtils::splitStringCopy(valueText(ClustersField).toUTF8(), " ")};
scanSettings.modify()->setClusterTypes(LmsApp->getDbSession(), std::set<std::string>(clusterTypes.begin(), clusterTypes.end()));
}
@@ -215,8 +215,8 @@ DatabaseSettingsView::refreshView()
recommendationEngineType->setModel(model->recommendationEngineTypeModel());
t->setFormWidget(DatabaseSettingsModel::RecommendationEngineTypeField, std::move(recommendationEngineType));
// Tags
t->setFormWidget(DatabaseSettingsModel::TagsField, std::make_unique<Wt::WLineEdit>());
// Clusters
t->setFormWidget(DatabaseSettingsModel::ClustersField, std::make_unique<Wt::WLineEdit>());
// Buttons
Wt::WPushButton *saveBtn = t->bindWidget("apply-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.apply")));
+2 -2
View File
@@ -21,7 +21,7 @@
#include <Wt/WAnchor.h>
#include "services/database/Artist.hpp"
#include "LmsApplication.hpp"
#include "Utils.hpp"
namespace UserInterface::ArtistListHelpers
{
@@ -29,7 +29,7 @@ namespace UserInterface::ArtistListHelpers
createEntry(const Database::ObjectPtr<Database::Artist>& artist)
{
auto res {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry"))};
res->bindWidget("name", LmsApplication::createArtistAnchor(artist));
res->bindWidget("name", Utils::createArtistAnchor(artist));
return res;
}
+1 -1
View File
@@ -335,7 +335,7 @@ Artist::addSomeNonReleaseTracks()
}
const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)};
_trackContainer->add(TrackListHelpers::createEntry(track, _playQueueController));
_trackContainer->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
areTracksAdded = true;
}
+1 -1
View File
@@ -137,7 +137,7 @@ Artists::refreshArtistLinkTypes()
if (!usedLinkTypes.contains(linkType))
return;
linkTypeModel->add(Wt::WString::tr(std::string {stringKey}), linkType);
linkTypeModel->add(Wt::WString::trn(std::string {stringKey}, 2), linkType);
}};
linkTypeModel->clear();
+3 -4
View File
@@ -26,7 +26,6 @@
#include "services/database/Artist.hpp"
#include "services/database/Release.hpp"
#include "LmsApplication.hpp"
#include "Utils.hpp"
using namespace Database;
@@ -39,11 +38,11 @@ namespace UserInterface::ReleaseListHelpers
{
auto entry {std::make_unique<Wt::WTemplate>(Wt::WString::tr(templateKey))};
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
entry->bindWidget("release-name", Utils::createReleaseAnchor(release));
entry->addFunction("tr", &Wt::WTemplate::Functions::tr);
{
Wt::WAnchor* anchor {entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false))};
Wt::WAnchor* anchor {entry->bindWidget("cover", Utils::createReleaseAnchor(release, false))};
auto cover {Utils::createCover(release->getId(), CoverResource::Size::Large)};
cover->addStyleClass("Lms-cover-release Lms-cover-anchor");
anchor->setImage(std::move(cover));
@@ -62,7 +61,7 @@ namespace UserInterface::ReleaseListHelpers
else if (artists.size() == 1 && !isSameArtist)
{
entry->setCondition("if-has-artist", true);
entry->bindWidget("artist-name", LmsApplication::createArtistAnchor(artists.front()));
entry->bindWidget("artist-name", Utils::createArtistAnchor(artists.front()));
}
if (showYear)
+6 -2
View File
@@ -37,6 +37,7 @@
#include "explore/Filters.hpp"
#include "explore/PlayQueueController.hpp"
#include "explore/ReleaseListHelpers.hpp"
#include "explore/TrackListHelpers.hpp"
#include "LmsApplication.hpp"
#include "LmsApplicationException.hpp"
#include "MediaPlayer.hpp"
@@ -270,7 +271,7 @@ Release::refreshView()
{
if (!firstArtist)
artistsContainer->addNew<Wt::WText>(" · ");
auto anchor {LmsApplication::createArtistAnchor(artist)};
auto anchor {Utils::createArtistAnchor(artist)};
anchor->addStyleClass("link-success text-decoration-none"); // hack
artistsContainer->addWidget(std::move(anchor));
firstArtist = false;
@@ -319,6 +320,9 @@ Release::refreshView()
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
->setLink(Wt::WLink {std::make_unique<DownloadTrackResource>(trackId)});
entry->bindNew<Wt::WPushButton>("track-info", Wt::WString::tr("Lms.Explore.track-info"))
->clicked().connect([=] { TrackListHelpers::showTrackInfoModal(trackId, _filters); });
}
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
@@ -361,7 +365,7 @@ Release::refreshReleaseArtists(const Database::Release::pointer& release)
for (const auto& artist : artists)
{
Wt::WTemplate* artistTemplate {artistsContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-release-artist"))};
artistTemplate->bindWidget("artist", LmsApplication::createArtistAnchor(artist));
artistTemplate->bindWidget("artist", Utils::createArtistAnchor(artist));
}
}
}
+2 -1
View File
@@ -38,6 +38,7 @@ namespace UserInterface
SearchView::SearchView(Filters& filters, PlayQueueController& playQueueController)
: Wt::WTemplate {Wt::WString::tr("Lms.Explore.Search.template")}
, _playQueueController {playQueueController}
, _filters {filters}
, _artistCollector {filters, ArtistCollector::Mode::Search, getMaxCount(Mode::Artist)}
, _releaseCollector {filters, ReleaseCollector::Mode::Search, getMaxCount(Mode::Release)}
, _trackCollector {filters, TrackCollector::Mode::Search, getMaxCount(Mode::Track)}
@@ -152,7 +153,7 @@ namespace UserInterface
for (const TrackId trackId : trackIds.results)
{
const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)};
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController));
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
}
}
+1
View File
@@ -72,6 +72,7 @@ namespace UserInterface
void addSomeTracks();
PlayQueueController& _playQueueController;
Filters& _filters;
ArtistCollector _artistCollector;
ReleaseCollector _releaseCollector;
TrackCollector _trackCollector;
+75 -4
View File
@@ -23,6 +23,7 @@
#include <Wt/WImage.h>
#include <Wt/WPushButton.h>
#include "av/IAudioFile.hpp"
#include "services/database/Artist.hpp"
#include "services/database/Release.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
@@ -36,29 +37,96 @@
#include "resource/CoverResource.hpp"
#include "LmsApplication.hpp"
#include "MediaPlayer.hpp"
#include "ModalManager.hpp"
#include "Utils.hpp"
using namespace Database;
namespace UserInterface::TrackListHelpers
{
void
showTrackInfoModal(Database::TrackId trackId, Filters& filters)
{
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
const Database::Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)};
if (!track)
return;
auto trackInfo {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.Tracks.template.track-info"))};
Wt::WWidget* trackInfoPtr {trackInfo.get()};
trackInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
Wt::WContainerWidget* artistTable {trackInfo->bindNew<Wt::WContainerWidget>("artist-table")};
auto addArtists = [&](TrackArtistLinkType linkType, const char* type)
{
Artist::FindParameters params;
params.setTrack(trackId);
params.setLinkType(linkType);
const auto artistIds {Artist::find(LmsApp->getDbSession(), params)};
if (artistIds.results.empty())
return;
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistContainer(artistIds.results)};
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.Tracks.template.track-info.artists"))};
artistsEntry->bindString("type", Wt::WString::trn(type, artistContainer->count()));
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
artistTable->addWidget(std::move(artistsEntry));
};
addArtists(TrackArtistLinkType::Composer, "Lms.Explore.Artists.linktype-composer");
addArtists(TrackArtistLinkType::Lyricist, "Lms.Explore.Artists.linktype-lyricist");
addArtists(TrackArtistLinkType::Mixer, "Lms.Explore.Artists.linktype-mixer");
addArtists(TrackArtistLinkType::Remixer, "Lms.Explore.Artists.linktype-remixer");
addArtists(TrackArtistLinkType::Producer, "Lms.Explore.Artists.linktype-producer");
if (const auto audioFile {Av::parseAudioFile(track->getPath())})
{
const std::optional<Av::StreamInfo> audioStream {audioFile->getBestStreamInfo()};
if (audioStream)
{
trackInfo->setCondition("if-has-codec", true);
trackInfo->bindString("codec", audioStream->codec);
if (audioStream->bitrate)
{
trackInfo->setCondition("if-has-bitrate", true);
trackInfo->bindString("bitrate", std::to_string(audioStream->bitrate / 1000) + " kbps");
}
}
}
trackInfo->bindString("duration", Utils::durationToString(track->getDuration()));
Wt::WContainerWidget* clusterContainer {trackInfo->bindWidget("clusters", Utils::createClustersForTrack(track, filters))};
if (clusterContainer->count() > 0)
trackInfo->setCondition("if-has-clusters", true);
Wt::WPushButton* okBtn {trackInfo->bindNew<Wt::WPushButton>("ok-btn", Wt::WString::tr("Lms.ok"))};
okBtn->clicked().connect([=]
{
LmsApp->getModalManager().dispose(trackInfoPtr);
});
LmsApp->getModalManager().show(std::move(trackInfo));
}
std::unique_ptr<Wt::WWidget>
createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueController& playQueueController)
createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueController& playQueueController, Filters& filters)
{
auto entry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))};
auto* entryPtr {entry.get()};
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
const auto artists {track->getArtists({TrackArtistLinkType::Artist})};
const Release::pointer release {track->getRelease()};
const TrackId trackId {track->getId()};
if (track->getRelease())
{
entry->setCondition("if-has-release", true);
entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease()));
Wt::WAnchor* anchor {entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false))};
entry->bindWidget("release", Utils::createReleaseAnchor(track->getRelease()));
Wt::WAnchor* anchor {entry->bindWidget("cover", Utils::createReleaseAnchor(release, false))};
auto cover {Utils::createCover(release->getId(), CoverResource::Size::Small)};
cover->addStyleClass("Lms-cover-track Lms-cover-anchor"); // HACK
anchor->setImage(std::move((cover)));
@@ -109,6 +177,9 @@ namespace UserInterface::TrackListHelpers
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
->setLink(Wt::WLink {std::make_unique<DownloadTrackResource>(trackId)});
entry->bindNew<Wt::WPushButton>("track-info", Wt::WString::tr("Lms.Explore.track-info"))
->clicked().connect([trackId, &filters] { showTrackInfoModal(trackId, filters); });
LmsApp->getMediaPlayer().trackLoaded.connect(entryPtr, [=] (Database::TrackId loadedTrackId)
{
entryPtr->toggleStyleClass("Lms-entry-playing", loadedTrackId == trackId);
+4 -1
View File
@@ -23,6 +23,7 @@
#include <Wt/WWidget.h>
#include "services/database/Object.hpp"
#include "services/database/TrackId.hpp"
namespace Database
{
@@ -32,10 +33,12 @@ namespace Database
namespace UserInterface
{
class PlayQueueController;
class Filters;
}
namespace UserInterface::TrackListHelpers
{
std::unique_ptr<Wt::WWidget> createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueController& playQueueController);
void showTrackInfoModal(Database::TrackId trackId, Filters& filters);
std::unique_ptr<Wt::WWidget> createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueController& playQueueController, Filters& filters);
} // namespace UserInterface
+1 -1
View File
@@ -199,7 +199,7 @@ namespace UserInterface
for (const TrackId trackId : trackIds.results)
{
if (const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)})
_container->add(TrackListHelpers::createEntry(track, _playQueueController));
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
}
_container->setHasMore(trackIds.moreResults && _container->getCount() < _maxCount);
+1 -1
View File
@@ -136,7 +136,7 @@ namespace UserInterface
const TrackListId trackListId {trackList->getId()};
WTemplate* entry {_container->addNew<Template>(Wt::WString::tr("Lms.Explore.TrackLists.template.entry"))};
entry->bindWidget("name", LmsApplication::createTrackListAnchor(trackList));
entry->bindWidget("name", Utils::createTrackListAnchor(trackList));
assert(_trackListWidgets.find(trackListId) == std::cend(_trackListWidgets));
_trackListWidgets.emplace(trackListId, entry);
+2 -1
View File
@@ -37,6 +37,7 @@ namespace UserInterface {
Tracks::Tracks(Filters& filters, PlayQueueController& playQueueController)
: Template {Wt::WString::tr("Lms.Explore.Tracks.template")}
, _filters {filters}
, _playQueueController {playQueueController}
, _trackCollector {filters, _defaultMode, _maxCount}
{
@@ -124,7 +125,7 @@ Tracks::addSome()
for (const TrackId trackId : trackIds.results)
{
if (const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)})
_container->add(TrackListHelpers::createEntry(track, _playQueueController));
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
}
_container->setHasMore(trackIds.moreResults);
+1
View File
@@ -46,6 +46,7 @@ namespace UserInterface
static constexpr std::size_t _batchSize {6};
static constexpr std::size_t _maxCount {8000};
Filters& _filters;
PlayQueueController& _playQueueController;
Wt::WWidget* _currentActiveItem {};
InfiniteScrollingContainer* _container {};