Added a message box to display some track information. closes #276
This commit is contained in:
@@ -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 {};
|
||||
|
||||
|
||||
@@ -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 {};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user