Deduplicate embedded images, ref #628
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
template<typename Tag, typename T>
|
||||
class TaggedType
|
||||
{
|
||||
public:
|
||||
using underlying_type = T;
|
||||
|
||||
explicit constexpr TaggedType() = default;
|
||||
explicit constexpr TaggedType(T value)
|
||||
: _value{ value } {}
|
||||
|
||||
constexpr T value() const { return _value; }
|
||||
|
||||
auto operator<=>(const TaggedType&) const = default;
|
||||
|
||||
private:
|
||||
T _value{};
|
||||
};
|
||||
} // namespace lms::core
|
||||
@@ -25,6 +25,8 @@ add_library(lmsdatabase STATIC
|
||||
impl/SqlQuery.cpp
|
||||
impl/Track.cpp
|
||||
impl/TrackBookmark.cpp
|
||||
impl/TrackEmbeddedImage.cpp
|
||||
impl/TrackEmbeddedImageLink.cpp
|
||||
impl/TrackLyrics.cpp
|
||||
impl/Types.cpp
|
||||
impl/UIState.cpp
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "PathTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/PathTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Session.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "PathTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/PathTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "PathTraits.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/PathTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 83 };
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 84 };
|
||||
}
|
||||
|
||||
VersionInfo::VersionInfo()
|
||||
@@ -1101,11 +1101,42 @@ FROM tracklist)");
|
||||
|
||||
void migrateFromV82(Session& session)
|
||||
{
|
||||
// new setting to display inline artist relationships in the release view
|
||||
// New setting to display inline artist relationships in the release view
|
||||
utils::executeCommand(*session.getDboSession(), "ALTER TABLE user ADD COLUMN ui_enable_inline_artist_relationships BOOLEAN NOT NULL DEFAULT(false)");
|
||||
utils::executeCommand(*session.getDboSession(), "ALTER TABLE user ADD COLUMN ui_inline_artist_relationships BIGINT NOT NULL DEFAULT(68)"); // Composer + Performer
|
||||
}
|
||||
|
||||
void migrateFromV83(Session& session)
|
||||
{
|
||||
// New embedded track image handling
|
||||
utils::executeCommand(*session.getDboSession(), "ALTER TABLE track DROP COLUMN has_cover");
|
||||
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "track_embedded_image" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"hash" text not null,
|
||||
"size" integer not null,
|
||||
"width" integer not null,
|
||||
"height" integer not null,
|
||||
"mime_type" text not null
|
||||
))");
|
||||
|
||||
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "track_embedded_image_link" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"index" integer not null,
|
||||
"is_preferred" boolean not null,
|
||||
"type" integer not null,
|
||||
"description" text not null,
|
||||
"track_id" bigint,
|
||||
"track_embedded_image_id" bigint,
|
||||
constraint "fk_track_embedded_image_link_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred,
|
||||
constraint "fk_track_embedded_image_link_track_embedded_image" foreign key ("track_embedded_image_id") references "track_embedded_image" ("id") on delete cascade deferrable initially deferred
|
||||
))");
|
||||
|
||||
// Just increment the scan version of the settings to make the next scan rescan everything
|
||||
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
|
||||
}
|
||||
|
||||
bool doDbMigration(Session& session)
|
||||
{
|
||||
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
|
||||
@@ -1165,6 +1196,7 @@ FROM tracklist)");
|
||||
{ 80, migrateFromV80 },
|
||||
{ 81, migrateFromV81 },
|
||||
{ 82, migrateFromV82 },
|
||||
{ 83, migrateFromV83 },
|
||||
};
|
||||
|
||||
bool migrationPerformed{};
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "PathTraits.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/PathTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -31,12 +31,12 @@
|
||||
#include "database/Types.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "EnumSetTraits.hpp"
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "PartialDateTimeTraits.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/EnumSetTraits.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/PartialDateTimeTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackArtistLink.hpp"
|
||||
#include "database/TrackBookmark.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/TrackFeatures.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/TrackLyrics.hpp"
|
||||
@@ -50,11 +52,12 @@
|
||||
#include "database/UIState.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "EnumSetTraits.hpp"
|
||||
#include "Migration.hpp"
|
||||
#include "PartialDateTimeTraits.hpp"
|
||||
#include "PathTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/EnumSetTraits.hpp"
|
||||
#include "traits/ImageHashTypeTraits.hpp"
|
||||
#include "traits/PartialDateTimeTraits.hpp"
|
||||
#include "traits/PathTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
@@ -122,6 +125,8 @@ namespace lms::db
|
||||
_session.mapClass<Track>("track");
|
||||
_session.mapClass<TrackBookmark>("track_bookmark");
|
||||
_session.mapClass<TrackArtistLink>("track_artist_link");
|
||||
_session.mapClass<TrackEmbeddedImage>("track_embedded_image");
|
||||
_session.mapClass<TrackEmbeddedImageLink>("track_embedded_image_link");
|
||||
_session.mapClass<TrackFeatures>("track_features");
|
||||
_session.mapClass<TrackList>("tracklist");
|
||||
_session.mapClass<TrackListEntry>("tracklist_entry");
|
||||
@@ -209,6 +214,13 @@ namespace lms::db
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_media_library_idx ON directory(media_library_id)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS directory_name_idx ON directory(name COLLATE NOCASE)");
|
||||
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_id_idx ON track_embedded_image(id)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_hash_idx ON track_embedded_image(hash)");
|
||||
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_track_id_idx ON track_embedded_image_link(track_id)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_track_embedded_image_id_track_id_idx ON track_embedded_image_link(track_embedded_image_id, track_id)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_is_preferred_track_id_track_embedded_image_id_idx ON track_embedded_image_link(is_preferred, track_id, track_embedded_image_id)");
|
||||
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_directory_stem_idx ON image(directory_id, stem COLLATE NOCASE)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_id_idx ON image(id)");
|
||||
utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_path_idx ON image(absolute_file_path)");
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -29,16 +29,18 @@
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/TrackArtistLink.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/TrackFeatures.hpp"
|
||||
#include "database/TrackLyrics.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "PartialDateTimeTraits.hpp"
|
||||
#include "PathTraits.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/PartialDateTimeTraits.hpp"
|
||||
#include "traits/PathTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
@@ -177,12 +179,15 @@ namespace lms::db
|
||||
if (params.directory.isValid())
|
||||
query.where("t.directory_id = ?").bind(params.directory);
|
||||
|
||||
if (params.hasEmbeddedImage.has_value())
|
||||
query.where("t.has_cover = ?").bind(params.hasEmbeddedImage.value());
|
||||
|
||||
if (params.fileSize.has_value())
|
||||
query.where("t.file_size = ?").bind(static_cast<long long>(params.fileSize.value()));
|
||||
|
||||
if (params.embeddedImageId.isValid())
|
||||
{
|
||||
query.join("track_embedded_image_link t_e_i_l ON t_e_i_l.track_id = t.id");
|
||||
query.where("t_e_i_l.track_embedded_image_id = ?").bind(params.embeddedImageId);
|
||||
}
|
||||
|
||||
switch (params.sortMethod)
|
||||
{
|
||||
case TrackSortMethod::None:
|
||||
@@ -469,6 +474,16 @@ namespace lms::db
|
||||
_trackLyrics.insert(getDboPtr(lyrics));
|
||||
}
|
||||
|
||||
void Track::clearEmbeddedImageLinks()
|
||||
{
|
||||
_embeddedImageLinks.clear();
|
||||
}
|
||||
|
||||
void Track::addEmbeddedImageLink(const ObjectPtr<TrackEmbeddedImageLink>& image)
|
||||
{
|
||||
_embeddedImageLinks.insert(getDboPtr(image));
|
||||
}
|
||||
|
||||
std::optional<int> Track::getYear() const
|
||||
{
|
||||
return _date.getYear();
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "database/Directory.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/ImageHashTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
Wt::Dbo::Query<Wt::Dbo::ptr<TrackEmbeddedImage>> createQuery(Session& session, const TrackEmbeddedImage::FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<TrackEmbeddedImage>>("SELECT t_e_i FROM track_embedded_image t_e_i") };
|
||||
|
||||
if (params.isPreferred
|
||||
|| params.track.isValid()
|
||||
|| params.release.isValid()
|
||||
|| params.trackList.isValid()
|
||||
|| params.sortMethod == TrackEmbeddedImageSortMethod::FrontCoverAndSize)
|
||||
{
|
||||
query.join("track_embedded_image_link t_e_i_l ON t_e_i_l.track_embedded_image_id = t_e_i.id");
|
||||
|
||||
if (params.isPreferred)
|
||||
query.where("t_e_i_l.is_preferred = ?").bind(params.isPreferred.value());
|
||||
|
||||
if (params.track.isValid())
|
||||
query.where("t_e_i_l.track_id = ?").bind(params.track);
|
||||
|
||||
if (params.release.isValid())
|
||||
{
|
||||
query.join("track t ON t_e_i_l.track_id = t.id");
|
||||
query.where("t.release_id = ?").bind(params.release);
|
||||
}
|
||||
|
||||
if (params.trackList.isValid())
|
||||
{
|
||||
query.join("tracklist_entry t_l_e ON t_l_e.track_id = t_e_i_l.track_id");
|
||||
query.where("t_l_e.tracklist_id = ?").bind(params.trackList);
|
||||
}
|
||||
}
|
||||
|
||||
switch (params.sortMethod)
|
||||
{
|
||||
case TrackEmbeddedImageSortMethod::None:
|
||||
break;
|
||||
case TrackEmbeddedImageSortMethod::FrontCoverAndSize:
|
||||
query.orderBy("CASE WHEN t_e_i_l.type = ? THEN 0 ELSE 1 END, t_e_i.size").bind(ImageType::FrontCover);
|
||||
break;
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TrackEmbeddedImage::pointer TrackEmbeddedImage::create(Session& session)
|
||||
{
|
||||
return session.getDboSession()->add(std::unique_ptr<TrackEmbeddedImage>{ new TrackEmbeddedImage{} });
|
||||
}
|
||||
|
||||
std::size_t TrackEmbeddedImage::getCount(Session& session)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_embedded_image"));
|
||||
}
|
||||
|
||||
TrackEmbeddedImage::pointer TrackEmbeddedImage::find(Session& session, TrackEmbeddedImageId id)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<TrackEmbeddedImage>().where("id = ?").bind(id));
|
||||
}
|
||||
|
||||
void TrackEmbeddedImage::find(Session& session, const FindParameters& params, const std::function<void(const pointer&)>& func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery(session, params) };
|
||||
utils::forEachQueryRangeResult(query, params.range, func);
|
||||
}
|
||||
|
||||
TrackEmbeddedImage::pointer TrackEmbeddedImage::find(Session& session, std::size_t size, ImageHashType hash)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ session.getDboSession()->find<TrackEmbeddedImage>().where("size = ?").bind(static_cast<int>(size)).where("hash = ?").bind(hash) };
|
||||
return utils::fetchQuerySingleResult(query);
|
||||
}
|
||||
|
||||
RangeResults<TrackEmbeddedImageId> TrackEmbeddedImage::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ session.getDboSession()->query<TrackEmbeddedImageId>("SELECT t_e_i.id FROM track_embedded_image t_e_i LEFT JOIN track_embedded_image_link t_e_i_l ON t_e_i.id = t_e_i_l.track_embedded_image_id WHERE t_e_i_l.track_embedded_image_id IS NULL") };
|
||||
return utils::execRangeQuery<TrackEmbeddedImageId>(query, range);
|
||||
}
|
||||
|
||||
} // namespace lms::db
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "database/TrackEmbeddedImageLink.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
TrackEmbeddedImageLink::TrackEmbeddedImageLink(ObjectPtr<Track> track, ObjectPtr<TrackEmbeddedImage> image)
|
||||
: _track{ getDboPtr(track) }
|
||||
, _image{ getDboPtr(image) }
|
||||
{
|
||||
}
|
||||
|
||||
TrackEmbeddedImageLink::pointer TrackEmbeddedImageLink::create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackEmbeddedImage> image)
|
||||
{
|
||||
return session.getDboSession()->add(std::unique_ptr<TrackEmbeddedImageLink>{ new TrackEmbeddedImageLink{ track, image } });
|
||||
}
|
||||
|
||||
std::size_t TrackEmbeddedImageLink::getCount(Session& session)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_embedded_image_link"));
|
||||
}
|
||||
|
||||
TrackEmbeddedImageLink::pointer TrackEmbeddedImageLink::find(Session& session, TrackEmbeddedImageLinkId id)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<TrackEmbeddedImageLink>().where("id = ?").bind(id));
|
||||
}
|
||||
|
||||
ObjectPtr<Track> TrackEmbeddedImageLink::getTrack() const
|
||||
{
|
||||
return _track;
|
||||
}
|
||||
|
||||
ObjectPtr<TrackEmbeddedImage> TrackEmbeddedImageLink::getImage() const
|
||||
{
|
||||
return _image;
|
||||
}
|
||||
} // namespace lms::db
|
||||
@@ -27,8 +27,8 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "PathTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/PathTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -19,13 +19,12 @@
|
||||
|
||||
#include "database/UIState.hpp"
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
#include "database/Track.hpp"
|
||||
#include "database/UIState.hpp"
|
||||
|
||||
#include "EnumSetTraits.hpp"
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "traits/EnumSetTraits.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/StringViewTraits.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Wt/Dbo/StdSqlTraits.h>
|
||||
|
||||
#include "core/String.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace Wt::Dbo
|
||||
{
|
||||
template<>
|
||||
struct sql_value_traits<lms::db::ImageHashType>
|
||||
{
|
||||
static const bool specialized = true;
|
||||
// Uses an underlying string to encode this big value
|
||||
|
||||
static std::string type(SqlConnection* conn, int size)
|
||||
{
|
||||
return sql_value_traits<std::string, void>::type(conn, size);
|
||||
}
|
||||
|
||||
static void bind(const lms::db::ImageHashType& v, SqlStatement* statement, int column, int size)
|
||||
{
|
||||
std::string valueAsStr{ std::to_string(v.value()) };
|
||||
sql_value_traits<std::string>::bind(valueAsStr, statement, column, size);
|
||||
}
|
||||
|
||||
static bool read(lms::db::ImageHashType& v, SqlStatement* statement, int column, int size)
|
||||
{
|
||||
std::string valueAsStr;
|
||||
if (sql_value_traits<std::string>::read(valueAsStr, statement, column, size))
|
||||
{
|
||||
if (const auto parsedValue{ lms::core::stringUtils::readAs<lms::db::ImageHashType::underlying_type>(valueAsStr) })
|
||||
{
|
||||
v = lms::db::ImageHashType{ *parsedValue };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
v = lms::db::ImageHashType{};
|
||||
return false;
|
||||
}
|
||||
};
|
||||
} // namespace Wt::Dbo
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "database/MediaLibraryId.hpp"
|
||||
#include "database/Object.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/TrackEmbeddedImageId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "database/TrackListId.hpp"
|
||||
#include "database/Types.hpp"
|
||||
@@ -52,6 +53,7 @@ namespace lms::db
|
||||
class Cluster;
|
||||
class ClusterType;
|
||||
class Directory;
|
||||
class TrackEmbeddedImageLink;
|
||||
class MediaLibrary;
|
||||
class Release;
|
||||
class Session;
|
||||
@@ -85,8 +87,8 @@ namespace lms::db
|
||||
std::optional<int> trackNumber; // matching this track number
|
||||
std::optional<int> discNumber; // matching this disc number
|
||||
DirectoryId directory; // if set, tracks in this directory
|
||||
std::optional<bool> hasEmbeddedImage; // if set, tracks that have or not embedded images
|
||||
std::optional<std::size_t> fileSize; // if set, tracks that match this file size
|
||||
TrackEmbeddedImageId embeddedImageId; // if set, tracks that have this embedded image
|
||||
|
||||
FindParameters& setFilters(const Filters& _filters)
|
||||
{
|
||||
@@ -182,16 +184,16 @@ namespace lms::db
|
||||
directory = _directory;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setHasEmbeddedImage(std::optional<bool> _hasEmbeddedImage)
|
||||
{
|
||||
hasEmbeddedImage = _hasEmbeddedImage;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setFileSize(std::optional<std::size_t> _fileSize)
|
||||
{
|
||||
fileSize = _fileSize;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setEmbeddedImage(TrackEmbeddedImageId _embeddedImageId)
|
||||
{
|
||||
embeddedImageId = _embeddedImageId;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
Track() = default;
|
||||
@@ -232,7 +234,6 @@ namespace lms::db
|
||||
void setSampleRate(std::size_t channelCount) { _sampleRate = channelCount; }
|
||||
void setDate(const core::PartialDateTime& date) { _date = date; }
|
||||
void setOriginalDate(const core::PartialDateTime& date) { _originalDate = date; }
|
||||
void setHasCover(bool hasCover) { _hasCover = hasCover; }
|
||||
void setTrackMBID(const std::optional<core::UUID>& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; }
|
||||
void setRecordingMBID(const std::optional<core::UUID>& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; }
|
||||
void setCopyright(std::string_view copyright);
|
||||
@@ -249,6 +250,8 @@ namespace lms::db
|
||||
void clearLyrics();
|
||||
void clearEmbeddedLyrics();
|
||||
void addLyrics(const ObjectPtr<TrackLyrics>& lyrics);
|
||||
void clearEmbeddedImageLinks();
|
||||
void addEmbeddedImageLink(const ObjectPtr<TrackEmbeddedImageLink>& link);
|
||||
void setMediaLibrary(ObjectPtr<MediaLibrary> mediaLibrary) { _mediaLibrary = getDboPtr(mediaLibrary); }
|
||||
void setDirectory(ObjectPtr<Directory> directory) { _directory = getDboPtr(directory); }
|
||||
|
||||
@@ -273,7 +276,6 @@ namespace lms::db
|
||||
std::optional<int> getOriginalYear() const;
|
||||
const Wt::WDateTime& getLastWriteTime() const { return _fileLastWrite; }
|
||||
const Wt::WDateTime& getAddedTime() const { return _fileAdded; }
|
||||
bool hasCover() const { return _hasCover; }
|
||||
bool hasLyrics() const;
|
||||
std::optional<core::UUID> getTrackMBID() const { return core::UUID::fromString(_trackMBID); }
|
||||
std::optional<core::UUID> getRecordingMBID() const { return core::UUID::fromString(_recordingMBID); }
|
||||
@@ -321,7 +323,6 @@ namespace lms::db
|
||||
Wt::Dbo::field(a, _fileSize, "file_size");
|
||||
Wt::Dbo::field(a, _fileLastWrite, "file_last_write");
|
||||
Wt::Dbo::field(a, _fileAdded, "file_added");
|
||||
Wt::Dbo::field(a, _hasCover, "has_cover");
|
||||
Wt::Dbo::field(a, _trackMBID, "mbid");
|
||||
Wt::Dbo::field(a, _recordingMBID, "recording_mbid");
|
||||
Wt::Dbo::field(a, _copyright, "copyright");
|
||||
@@ -338,6 +339,7 @@ namespace lms::db
|
||||
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track");
|
||||
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _trackLyrics, Wt::Dbo::ManyToOne, "track");
|
||||
Wt::Dbo::hasMany(a, _embeddedImageLinks, Wt::Dbo::ManyToOne, "track");
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -368,7 +370,6 @@ namespace lms::db
|
||||
long long _fileSize{};
|
||||
Wt::WDateTime _fileLastWrite;
|
||||
Wt::WDateTime _fileAdded;
|
||||
bool _hasCover{};
|
||||
std::string _trackMBID;
|
||||
std::string _recordingMBID;
|
||||
std::string _copyright;
|
||||
@@ -384,6 +385,7 @@ namespace lms::db
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Cluster>> _clusters;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<TrackLyrics>> _trackLyrics;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<TrackEmbeddedImageLink>> _embeddedImageLinks;
|
||||
};
|
||||
|
||||
namespace Debug
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <Wt/Dbo/Dbo.h>
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/TrackEmbeddedImageId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "database/TrackListId.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class TrackEmbeddedImageLink;
|
||||
class Session;
|
||||
class Track;
|
||||
|
||||
class TrackEmbeddedImage final : public Object<TrackEmbeddedImage, TrackEmbeddedImageId>
|
||||
{
|
||||
public:
|
||||
TrackEmbeddedImage() = default;
|
||||
|
||||
struct FindParameters
|
||||
{
|
||||
std::optional<Range> range;
|
||||
TrackId track;
|
||||
ReleaseId release;
|
||||
TrackListId trackList;
|
||||
std::optional<bool> isPreferred;
|
||||
TrackEmbeddedImageSortMethod sortMethod{ TrackEmbeddedImageSortMethod::None };
|
||||
|
||||
FindParameters& setRange(std::optional<Range> _range)
|
||||
{
|
||||
range = _range;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setTrack(TrackId _track)
|
||||
{
|
||||
track = _track;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setRelease(ReleaseId _release)
|
||||
{
|
||||
release = _release;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setTrackList(TrackListId _trackList)
|
||||
{
|
||||
trackList = _trackList;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setIsPreferred(std::optional<bool> _isPreferred)
|
||||
{
|
||||
isPreferred = _isPreferred;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setSortMethod(TrackEmbeddedImageSortMethod _sortMethod)
|
||||
{
|
||||
sortMethod = _sortMethod;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// find
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, TrackEmbeddedImageId id);
|
||||
static void find(Session& session, const FindParameters& params, const std::function<void(const pointer&)>& func);
|
||||
static pointer find(Session& session, std::size_t size, ImageHashType hash);
|
||||
static RangeResults<TrackEmbeddedImageId> findOrphanIds(Session& session, std::optional<Range> range);
|
||||
|
||||
// getters
|
||||
ImageHashType getHash() const { return _hash; }
|
||||
std::size_t getSize() const { return _size; }
|
||||
std::size_t getWidth() const { return _width; }
|
||||
std::size_t getHeight() const { return _height; }
|
||||
std::string_view getMimeType() const { return _mimeType; }
|
||||
|
||||
// setters
|
||||
void setHash(ImageHashType hash) { _hash = hash; }
|
||||
void setSize(std::size_t size) { _size = static_cast<int>(size); }
|
||||
void setWidth(std::size_t width) { _width = static_cast<int>(width); }
|
||||
void setHeight(std::size_t height) { _height = static_cast<int>(height); }
|
||||
void setMimeType(std::string_view mimeType) { _mimeType = mimeType; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _hash, "hash");
|
||||
Wt::Dbo::field(a, _size, "size");
|
||||
Wt::Dbo::field(a, _width, "width");
|
||||
Wt::Dbo::field(a, _height, "height");
|
||||
Wt::Dbo::field(a, _mimeType, "mime_type");
|
||||
|
||||
Wt::Dbo::hasMany(a, _links, Wt::Dbo::ManyToOne, "track_embedded_image");
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Session;
|
||||
|
||||
static pointer create(Session& session);
|
||||
|
||||
ImageHashType _hash;
|
||||
int _size{};
|
||||
int _width{};
|
||||
int _height{};
|
||||
std::string _mimeType;
|
||||
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<TrackEmbeddedImageLink>> _links;
|
||||
};
|
||||
} // namespace lms::db
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database/IdType.hpp"
|
||||
|
||||
LMS_DECLARE_IDTYPE(TrackEmbeddedImageId)
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <Wt/Dbo/Dbo.h>
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/TrackEmbeddedImageLinkId.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class TrackEmbeddedImage;
|
||||
class Session;
|
||||
class Track;
|
||||
|
||||
class TrackEmbeddedImageLink final : public Object<TrackEmbeddedImageLink, TrackEmbeddedImageLinkId>
|
||||
{
|
||||
public:
|
||||
TrackEmbeddedImageLink() = default;
|
||||
|
||||
using HashType = std::uint64_t;
|
||||
|
||||
// find
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, TrackEmbeddedImageLinkId id);
|
||||
|
||||
// getters
|
||||
ObjectPtr<Track> getTrack() const;
|
||||
ObjectPtr<TrackEmbeddedImage> getImage() const;
|
||||
std::size_t getIndex() const { return _index; }
|
||||
bool isPreferred() const { return _isPreferred; }
|
||||
ImageType getType() const { return _type; }
|
||||
std::string_view getDescription() const { return _description; }
|
||||
|
||||
// setters
|
||||
void setIndex(std::size_t index) { _index = static_cast<int>(index); }
|
||||
void setIsPreferred(bool isPreferred) { _isPreferred = isPreferred; }
|
||||
void setType(ImageType type) { _type = type; }
|
||||
void setDescription(std::string_view description) { _description = description; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _index, "index");
|
||||
Wt::Dbo::field(a, _isPreferred, "is_preferred");
|
||||
Wt::Dbo::field(a, _type, "type");
|
||||
Wt::Dbo::field(a, _description, "description");
|
||||
|
||||
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::belongsTo(a, _image, "track_embedded_image", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
TrackEmbeddedImageLink(ObjectPtr<Track> track, ObjectPtr<TrackEmbeddedImage> image);
|
||||
|
||||
friend class Session;
|
||||
static pointer create(Session& session, ObjectPtr<Track> track, ObjectPtr<TrackEmbeddedImage> image);
|
||||
|
||||
int _index{}; // index within the track
|
||||
bool _isPreferred{};
|
||||
ImageType _type{ ImageType::Unknown };
|
||||
std::string _description;
|
||||
|
||||
Wt::Dbo::ptr<Track> _track;
|
||||
Wt::Dbo::ptr<TrackEmbeddedImage> _image;
|
||||
};
|
||||
} // namespace lms::db
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database/IdType.hpp"
|
||||
|
||||
LMS_DECLARE_IDTYPE(TrackEmbeddedImageLinkId)
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <Wt/WDate.h>
|
||||
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/TaggedType.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
@@ -136,6 +137,8 @@ namespace lms::db
|
||||
Name,
|
||||
};
|
||||
|
||||
using ImageHashType = core::TaggedType<class ImageHash, std::uint64_t>;
|
||||
|
||||
enum class LabelSortMethod
|
||||
{
|
||||
None,
|
||||
@@ -165,6 +168,12 @@ namespace lms::db
|
||||
Name,
|
||||
};
|
||||
|
||||
enum class TrackEmbeddedImageSortMethod
|
||||
{
|
||||
None,
|
||||
FrontCoverAndSize,
|
||||
};
|
||||
|
||||
enum class TrackListSortMethod
|
||||
{
|
||||
None,
|
||||
@@ -194,6 +203,32 @@ namespace lms::db
|
||||
EmbeddedFirst,
|
||||
};
|
||||
|
||||
enum class ImageType
|
||||
{
|
||||
Unknown = 0,
|
||||
Other = 1,
|
||||
FileIcon = 2,
|
||||
OtherFileIcon = 3,
|
||||
FrontCover = 4,
|
||||
BackCover = 5,
|
||||
LeafletPage = 6,
|
||||
Media = 7,
|
||||
LeadArtist = 8,
|
||||
Artist = 9,
|
||||
Conductor = 10,
|
||||
Band = 11,
|
||||
Composer = 12,
|
||||
Lyricist = 13,
|
||||
RecordingLocation = 14,
|
||||
DuringRecording = 15,
|
||||
DuringPerformance = 16,
|
||||
MovieScreenCapture = 17,
|
||||
ColouredFish = 18,
|
||||
Illustration = 19,
|
||||
BandLogo = 20,
|
||||
PublisherLogo = 21
|
||||
};
|
||||
|
||||
enum class TrackArtistLinkType
|
||||
{
|
||||
Artist = 0, // regular track artist
|
||||
|
||||
@@ -19,6 +19,7 @@ add_executable(test-database
|
||||
StarredTrack.cpp
|
||||
Track.cpp
|
||||
TrackBookmark.cpp
|
||||
TrackEmbeddedImage.cpp
|
||||
TrackFeatures.cpp
|
||||
TrackList.cpp
|
||||
TrackLyrics.cpp
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include "database/StarredArtist.hpp"
|
||||
#include "database/StarredRelease.hpp"
|
||||
#include "database/StarredTrack.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/TrackLyrics.hpp"
|
||||
#include "database/UIState.hpp"
|
||||
#include "database/User.hpp"
|
||||
@@ -344,6 +346,8 @@ VALUES
|
||||
EXPECT_FALSE(Cluster::find(session, ClusterId{}));
|
||||
EXPECT_FALSE(ClusterType::find(session, ClusterTypeId{}));
|
||||
EXPECT_FALSE(Directory::find(session, DirectoryId{}));
|
||||
EXPECT_FALSE(TrackEmbeddedImage::find(session, TrackEmbeddedImageId{}));
|
||||
EXPECT_FALSE(TrackEmbeddedImageLink::find(session, TrackEmbeddedImageLinkId{}));
|
||||
EXPECT_FALSE(Image::find(session, ImageId{}));
|
||||
EXPECT_FALSE(Label::find(session, LabelId{}));
|
||||
EXPECT_FALSE(Listen::find(session, ListenId{}));
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/Types.hpp"
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
namespace lms::db::tests
|
||||
{
|
||||
using ScopedTrackEmbeddedImage = ScopedEntity<db::TrackEmbeddedImage>;
|
||||
using ScopedTrackEmbeddedImageLink = ScopedEntity<db::TrackEmbeddedImageLink>;
|
||||
|
||||
TEST_F(DatabaseFixture, TrackEmbeddedImage)
|
||||
{
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(TrackEmbeddedImage::getCount(session), 0);
|
||||
}
|
||||
|
||||
ScopedTrackEmbeddedImage image{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(TrackEmbeddedImage::getCount(session), 1);
|
||||
|
||||
const TrackEmbeddedImage::pointer img{ TrackEmbeddedImage::find(session, image.getId()) };
|
||||
ASSERT_NE(img, TrackEmbeddedImage::pointer{});
|
||||
EXPECT_EQ(img->getHash(), db::ImageHashType{});
|
||||
EXPECT_EQ(img->getSize(), 0);
|
||||
EXPECT_EQ(img->getWidth(), 0);
|
||||
EXPECT_EQ(img->getHeight(), 0);
|
||||
EXPECT_EQ(img->getMimeType(), "");
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackEmbeddedImage::pointer img{ TrackEmbeddedImage::find(session, image.getId()) };
|
||||
ASSERT_NE(img, TrackEmbeddedImage::pointer{});
|
||||
img.modify()->setHash(db::ImageHashType{ std::numeric_limits<std::uint64_t>::max() });
|
||||
img.modify()->setSize(1024 * 1024);
|
||||
img.modify()->setWidth(640);
|
||||
img.modify()->setHeight(480);
|
||||
img.modify()->setMimeType("image/jpeg");
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const TrackEmbeddedImage::pointer img{ TrackEmbeddedImage::find(session, image.getId()) };
|
||||
ASSERT_NE(img, TrackEmbeddedImage::pointer{});
|
||||
EXPECT_EQ(img->getHash(), db::ImageHashType{ std::numeric_limits<std::uint64_t>::max() });
|
||||
EXPECT_EQ(img->getSize(), 1024 * 1024);
|
||||
EXPECT_EQ(img->getWidth(), 640);
|
||||
EXPECT_EQ(img->getHeight(), 480);
|
||||
EXPECT_EQ(img->getMimeType(), "image/jpeg");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, TrackEmbeddedImage_findByHash)
|
||||
{
|
||||
ScopedTrackEmbeddedImage image{ session };
|
||||
constexpr std::size_t size{ 1024 };
|
||||
constexpr db::ImageHashType hash{ 42 };
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackEmbeddedImage::pointer img{ TrackEmbeddedImage::find(session, image.getId()) };
|
||||
ASSERT_NE(img, TrackEmbeddedImage::pointer{});
|
||||
img.modify()->setHash(hash);
|
||||
img.modify()->setSize(size);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const TrackEmbeddedImage::pointer img{ TrackEmbeddedImage::find(session, size, hash) };
|
||||
ASSERT_NE(img, TrackEmbeddedImage::pointer{});
|
||||
EXPECT_EQ(image.getId(), img->getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const TrackEmbeddedImage::pointer img{ TrackEmbeddedImage::find(session, size + 1, hash) };
|
||||
EXPECT_EQ(img, TrackEmbeddedImage::pointer{});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams)
|
||||
{
|
||||
ScopedTrackEmbeddedImage image{ session };
|
||||
ScopedTrack track{ session };
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
ScopedTrackEmbeddedImageLink link{ session, track.lockAndGet(), image.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setIsPreferred(true);
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_FALSE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
link.get().modify()->setIsPreferred(true);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setIsPreferred(true);
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setIsPreferred(true);
|
||||
params.setRelease(release.getId());
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_FALSE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setIsPreferred(true);
|
||||
params.setRelease(release.getId());
|
||||
params.setSortMethod(TrackEmbeddedImageSortMethod::FrontCoverAndSize);
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setIsPreferred(true);
|
||||
params.setTrack(track.getId());
|
||||
params.setSortMethod(TrackEmbeddedImageSortMethod::FrontCoverAndSize);
|
||||
|
||||
bool visited{};
|
||||
TrackEmbeddedImage::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Track_findByEmbeddedImage)
|
||||
{
|
||||
ScopedTrackEmbeddedImage image{ session };
|
||||
ScopedTrack track{ session };
|
||||
ScopedTrackEmbeddedImageLink link{ session, track.lockAndGet(), image.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
Track::FindParameters params;
|
||||
params.setEmbeddedImage(image->getId());
|
||||
|
||||
bool visited{};
|
||||
Track::find(session, params, [&](const auto&) { visited = true; });
|
||||
EXPECT_TRUE(visited);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, TrackEmbeddedImage_findOrphans)
|
||||
{
|
||||
ScopedTrackEmbeddedImage image{ session };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto orphans{ TrackEmbeddedImage::findOrphanIds(session, std::nullopt) };
|
||||
ASSERT_EQ(orphans.results.size(), 1);
|
||||
EXPECT_EQ(orphans.results[0], image.getId());
|
||||
}
|
||||
|
||||
{
|
||||
ScopedTrack track{ session };
|
||||
ScopedTrackEmbeddedImageLink link{ session, track.lockAndGet(), image.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto orphans{ TrackEmbeddedImage::findOrphanIds(session, std::nullopt) };
|
||||
ASSERT_EQ(orphans.results.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto orphans{ TrackEmbeddedImage::findOrphanIds(session, std::nullopt) };
|
||||
ASSERT_EQ(orphans.results.size(), 1);
|
||||
EXPECT_EQ(orphans.results[0], image.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, TrackEmbeddedImageLink)
|
||||
{
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(TrackEmbeddedImage::getCount(session), 0);
|
||||
}
|
||||
|
||||
ScopedTrack track{ session };
|
||||
ScopedTrackEmbeddedImage image{ session };
|
||||
ScopedTrackEmbeddedImageLink imageLink{ session, track.lockAndGet(), image.lockAndGet() };
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(TrackEmbeddedImageLink::getCount(session), 1);
|
||||
|
||||
const TrackEmbeddedImageLink::pointer link{ TrackEmbeddedImageLink::find(session, imageLink.getId()) };
|
||||
ASSERT_NE(link, TrackEmbeddedImageLink::pointer{});
|
||||
EXPECT_EQ(link->getIndex(), 0);
|
||||
EXPECT_EQ(link->getType(), ImageType::Unknown);
|
||||
EXPECT_EQ(link->getDescription(), "");
|
||||
EXPECT_EQ(link->getTrack(), track.get());
|
||||
EXPECT_EQ(link->getImage(), image.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
TrackEmbeddedImageLink::pointer link{ TrackEmbeddedImageLink::find(session, imageLink.getId()) };
|
||||
ASSERT_NE(link, TrackEmbeddedImage::pointer{});
|
||||
link.modify()->setIndex(2);
|
||||
link.modify()->setType(ImageType::FrontCover);
|
||||
link.modify()->setDescription("MyDesc");
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const TrackEmbeddedImageLink::pointer img{ TrackEmbeddedImageLink::find(session, imageLink.getId()) };
|
||||
ASSERT_NE(img, TrackEmbeddedImage::pointer{});
|
||||
EXPECT_EQ(img->getIndex(), 2);
|
||||
EXPECT_EQ(img->getType(), ImageType::FrontCover);
|
||||
EXPECT_EQ(img->getDescription(), "MyDesc");
|
||||
}
|
||||
}
|
||||
} // namespace lms::db::tests
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "core/Utils.hpp"
|
||||
#include "database/Db.hpp"
|
||||
#include "database/Image.hpp"
|
||||
@@ -204,9 +203,9 @@ namespace lms::cover
|
||||
return cover;
|
||||
}
|
||||
|
||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getTrackImage(db::TrackId trackId, std::optional<image::ImageSize> width)
|
||||
std::shared_ptr<image::IEncodedImage> ArtworkService::getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<image::ImageSize> width)
|
||||
{
|
||||
const ImageCache::EntryDesc cacheEntryDesc{ trackId, width };
|
||||
const ImageCache::EntryDesc cacheEntryDesc{ trackEmbeddedImageId, width };
|
||||
|
||||
std::shared_ptr<image::IEncodedImage> cover{ _cache.getImage(cacheEntryDesc) };
|
||||
if (cover)
|
||||
@@ -217,12 +216,14 @@ namespace lms::cover
|
||||
db::Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const db::Track::pointer track{ db::Track::find(session, trackId) };
|
||||
if (track && track->hasCover())
|
||||
trackFile = track->getAbsoluteFilePath();
|
||||
db::Track::FindParameters params;
|
||||
params.setEmbeddedImage(trackEmbeddedImageId);
|
||||
db::Track::find(session, params, [&](const db::Track::pointer& track) {
|
||||
if (!cover)
|
||||
cover = getTrackImage(track->getAbsoluteFilePath(), width);
|
||||
});
|
||||
}
|
||||
|
||||
cover = getTrackImage(trackFile, width);
|
||||
if (cover)
|
||||
_cache.addImage(cacheEntryDesc, cover);
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "image/IEncodedImage.hpp"
|
||||
#include "services/artwork/IArtworkService.hpp"
|
||||
|
||||
#include "ImageCache.hpp"
|
||||
@@ -49,7 +48,8 @@ namespace lms::cover
|
||||
|
||||
private:
|
||||
std::shared_ptr<image::IEncodedImage> getImage(db::ImageId imageId, std::optional<image::ImageSize> width) override;
|
||||
std::shared_ptr<image::IEncodedImage> getTrackImage(db::TrackId trackId, std::optional<image::ImageSize> width) override;
|
||||
std::shared_ptr<image::IEncodedImage> getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<image::ImageSize> width) override;
|
||||
|
||||
std::shared_ptr<image::IEncodedImage> getDefaultReleaseCover() override;
|
||||
std::shared_ptr<image::IEncodedImage> getDefaultArtistImage() override;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <variant>
|
||||
|
||||
#include "database/ImageId.hpp"
|
||||
#include "database/TrackEmbeddedImageId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "image/IEncodedImage.hpp"
|
||||
|
||||
@@ -38,7 +39,7 @@ namespace lms::cover
|
||||
|
||||
struct EntryDesc
|
||||
{
|
||||
using VariantType = std::variant<db::TrackId, db::ImageId>;
|
||||
using VariantType = std::variant<db::TrackEmbeddedImageId, db::ImageId>;
|
||||
VariantType id;
|
||||
std::optional<std::size_t> size;
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <optional>
|
||||
|
||||
#include "database/ImageId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "database/TrackEmbeddedImageId.hpp"
|
||||
#include "image/IEncodedImage.hpp"
|
||||
|
||||
namespace lms::db
|
||||
@@ -40,9 +40,7 @@ namespace lms::cover
|
||||
virtual ~IArtworkService() = default;
|
||||
|
||||
virtual std::shared_ptr<image::IEncodedImage> getImage(db::ImageId imageId, std::optional<image::ImageSize> width) = 0;
|
||||
|
||||
// no logic to fallback to release here
|
||||
virtual std::shared_ptr<image::IEncodedImage> getTrackImage(db::TrackId trackId, std::optional<image::ImageSize> width) = 0;
|
||||
virtual std::shared_ptr<image::IEncodedImage> getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<image::ImageSize> width) = 0;
|
||||
|
||||
// Svg images dont have image "size"
|
||||
virtual std::shared_ptr<image::IEncodedImage> getDefaultReleaseCover() = 0;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "core/PartialDateTime.hpp"
|
||||
#include "core/Path.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/XxHash3.hpp"
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Cluster.hpp"
|
||||
#include "database/Db.hpp"
|
||||
@@ -34,6 +35,8 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackArtistLink.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/TrackFeatures.hpp"
|
||||
#include "database/TrackLyrics.hpp"
|
||||
#include "database/Types.hpp"
|
||||
@@ -330,6 +333,117 @@ namespace lms::scanner
|
||||
return lyrics;
|
||||
}
|
||||
|
||||
struct ImageInfo
|
||||
{
|
||||
std::size_t index;
|
||||
metadata::Image::Type type{ metadata::Image::Type::Unknown };
|
||||
std::uint64_t hash{};
|
||||
std::size_t size{};
|
||||
image::ImageProperties properties;
|
||||
std::string mimeType;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
db::ImageType convertImageType(metadata::Image::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case metadata::Image::Type::Unknown:
|
||||
return db::ImageType::Unknown;
|
||||
case metadata::Image::Type::Other:
|
||||
return db::ImageType::Other;
|
||||
case metadata::Image::Type::FileIcon:
|
||||
return db::ImageType::FileIcon;
|
||||
case metadata::Image::Type::OtherFileIcon:
|
||||
return db::ImageType::OtherFileIcon;
|
||||
case metadata::Image::Type::FrontCover:
|
||||
return db::ImageType::FrontCover;
|
||||
case metadata::Image::Type::BackCover:
|
||||
return db::ImageType::BackCover;
|
||||
case metadata::Image::Type::LeafletPage:
|
||||
return db::ImageType::LeafletPage;
|
||||
case metadata::Image::Type::Media:
|
||||
return db::ImageType::Media;
|
||||
case metadata::Image::Type::LeadArtist:
|
||||
return db::ImageType::LeadArtist;
|
||||
case metadata::Image::Type::Artist:
|
||||
return db::ImageType::Artist;
|
||||
case metadata::Image::Type::Conductor:
|
||||
return db::ImageType::Conductor;
|
||||
case metadata::Image::Type::Band:
|
||||
return db::ImageType::Band;
|
||||
case metadata::Image::Type::Composer:
|
||||
return db::ImageType::Composer;
|
||||
case metadata::Image::Type::Lyricist:
|
||||
return db::ImageType::Lyricist;
|
||||
case metadata::Image::Type::RecordingLocation:
|
||||
return db::ImageType::RecordingLocation;
|
||||
case metadata::Image::Type::DuringRecording:
|
||||
return db::ImageType::DuringRecording;
|
||||
case metadata::Image::Type::DuringPerformance:
|
||||
return db::ImageType::DuringPerformance;
|
||||
case metadata::Image::Type::MovieScreenCapture:
|
||||
return db::ImageType::MovieScreenCapture;
|
||||
case metadata::Image::Type::ColouredFish:
|
||||
return db::ImageType::ColouredFish;
|
||||
case metadata::Image::Type::Illustration:
|
||||
return db::ImageType::Illustration;
|
||||
case metadata::Image::Type::BandLogo:
|
||||
return db::ImageType::BandLogo;
|
||||
case metadata::Image::Type::PublisherLogo:
|
||||
return db::ImageType::PublisherLogo;
|
||||
}
|
||||
}
|
||||
|
||||
db::TrackEmbeddedImage::pointer getOrCreateTrackEmbeddedImage(db::Session& session, const ImageInfo& imageInfo)
|
||||
{
|
||||
db::TrackEmbeddedImage::pointer image{ db::TrackEmbeddedImage::find(session, imageInfo.size, db::ImageHashType{ imageInfo.hash }) };
|
||||
if (!image)
|
||||
{
|
||||
image = session.create<db::TrackEmbeddedImage>();
|
||||
image.modify()->setSize(imageInfo.size);
|
||||
image.modify()->setHash(db::ImageHashType{ imageInfo.hash });
|
||||
image.modify()->setWidth(imageInfo.properties.width);
|
||||
image.modify()->setHeight(imageInfo.properties.height);
|
||||
image.modify()->setMimeType(imageInfo.mimeType);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
db::TrackEmbeddedImageLink::pointer createTrackEmbeddedImageLink(db::Session& session, const db::Track::pointer& track, const ImageInfo& imageInfo)
|
||||
{
|
||||
const db::TrackEmbeddedImage::pointer image{ getOrCreateTrackEmbeddedImage(session, imageInfo) };
|
||||
db::TrackEmbeddedImageLink::pointer imageLink{ session.create<db::TrackEmbeddedImageLink>(track, image) };
|
||||
imageLink.modify()->setIndex(imageInfo.index);
|
||||
imageLink.modify()->setType(convertImageType(imageInfo.type));
|
||||
imageLink.modify()->setDescription(imageInfo.description);
|
||||
|
||||
return imageLink;
|
||||
}
|
||||
|
||||
void updateEmbeddedImages(db::Session& session, db::Track::pointer& track, std::span<const ImageInfo> images)
|
||||
{
|
||||
db::TrackEmbeddedImageLink::pointer preferredImageLink;
|
||||
|
||||
track.modify()->clearEmbeddedImageLinks();
|
||||
for (const ImageInfo& imageInfo : images)
|
||||
{
|
||||
db::TrackEmbeddedImageLink::pointer link{ createTrackEmbeddedImageLink(session, track, imageInfo) };
|
||||
track.modify()->addEmbeddedImageLink(link);
|
||||
|
||||
if (!preferredImageLink
|
||||
|| (preferredImageLink->getType() != db::ImageType::FrontCover && link->getType() == db::ImageType::FrontCover)
|
||||
|| (preferredImageLink->getImage()->getSize() < link->getImage()->getSize()))
|
||||
{
|
||||
preferredImageLink = link;
|
||||
}
|
||||
}
|
||||
|
||||
if (preferredImageLink)
|
||||
preferredImageLink.modify()->setIsPreferred(true);
|
||||
}
|
||||
|
||||
db::Advisory getAdvisory(std::optional<metadata::Track::Advisory> advisory)
|
||||
{
|
||||
if (!advisory)
|
||||
@@ -348,7 +462,7 @@ namespace lms::scanner
|
||||
return db::Advisory::UnSet;
|
||||
}
|
||||
|
||||
db::Track::pointer findMovedTrackBySizeAndMetaData(db::Session& session, const metadata::Track& parsedTrack, bool hasEmbeddedCover, const FileInfo& fileInfo)
|
||||
db::Track::pointer findMovedTrackBySizeAndMetaData(db::Session& session, const metadata::Track& parsedTrack, const FileInfo& fileInfo)
|
||||
{
|
||||
db::Track::FindParameters params;
|
||||
// Add as many fields as possible to limit errors
|
||||
@@ -362,7 +476,6 @@ namespace lms::scanner
|
||||
}
|
||||
if (parsedTrack.position)
|
||||
params.setTrackNumber(*parsedTrack.position);
|
||||
params.setHasEmbeddedImage(hasEmbeddedCover);
|
||||
params.setFileSize(fileInfo.fileSize);
|
||||
|
||||
bool error{};
|
||||
@@ -414,7 +527,7 @@ namespace lms::scanner
|
||||
metadata::IAudioFileParser& _parser;
|
||||
const ScannerSettings& _settings;
|
||||
std::unique_ptr<metadata::Track> _parsedTrack;
|
||||
bool _hasEmbeddedCover{};
|
||||
std::vector<ImageInfo> _parsedImages;
|
||||
};
|
||||
|
||||
void AudioFileScanOperation::scan()
|
||||
@@ -426,16 +539,32 @@ namespace lms::scanner
|
||||
{
|
||||
_parsedTrack = _parser.parseMetaData(_file);
|
||||
|
||||
std::size_t index{};
|
||||
_parser.parseImages(_file, [&](const metadata::Image& image) {
|
||||
try
|
||||
{
|
||||
image::probeImage(image.data);
|
||||
_hasEmbeddedCover = true;
|
||||
image::ImageProperties properties{ image::probeImage(image.data) };
|
||||
|
||||
ImageInfo info;
|
||||
info.index = index;
|
||||
info.type = image.type;
|
||||
{
|
||||
LMS_SCOPED_TRACE_DETAILED("Scanner", "ImageHash");
|
||||
info.hash = core::xxHash3_64(image.data);
|
||||
}
|
||||
info.size = image.data.size();
|
||||
info.mimeType = image.mimeType;
|
||||
info.description = image.description;
|
||||
info.properties = properties;
|
||||
|
||||
_parsedImages.push_back(std::move(info));
|
||||
}
|
||||
catch (const image::Exception& e)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, ERROR, "Failed to parse image in track file " << _file);
|
||||
}
|
||||
|
||||
index++;
|
||||
});
|
||||
}
|
||||
catch (const metadata::Exception& e)
|
||||
@@ -521,7 +650,7 @@ namespace lms::scanner
|
||||
if (!track)
|
||||
{
|
||||
// maybe the file just moved?
|
||||
track = findMovedTrackBySizeAndMetaData(dbSession, *_parsedTrack, _hasEmbeddedCover, *fileInfo);
|
||||
track = findMovedTrackBySizeAndMetaData(dbSession, *_parsedTrack, *fileInfo);
|
||||
if (track)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Considering track " << _file << " moved from " << track->getAbsoluteFilePath());
|
||||
@@ -662,7 +791,6 @@ namespace lms::scanner
|
||||
track.modify()->setTrackMBID(_parsedTrack->mbid);
|
||||
if (auto trackFeatures{ db::TrackFeatures::find(dbSession, track->getId()) })
|
||||
trackFeatures.remove(); // TODO: only if MBID changed?
|
||||
track.modify()->setHasCover(_hasEmbeddedCover);
|
||||
track.modify()->setCopyright(_parsedTrack->copyright);
|
||||
track.modify()->setCopyrightURL(_parsedTrack->copyrightURL);
|
||||
track.modify()->setAdvisory(getAdvisory(_parsedTrack->advisory));
|
||||
@@ -672,10 +800,9 @@ namespace lms::scanner
|
||||
|
||||
track.modify()->clearEmbeddedLyrics();
|
||||
for (const metadata::Lyrics& lyricsInfo : _parsedTrack->lyrics)
|
||||
{
|
||||
db::TrackLyrics::pointer lyrics{ createLyrics(dbSession, lyricsInfo) };
|
||||
track.modify()->addLyrics(lyrics);
|
||||
}
|
||||
track.modify()->addLyrics(createLyrics(dbSession, lyricsInfo));
|
||||
|
||||
updateEmbeddedImages(dbSession, track, _parsedImages);
|
||||
|
||||
if (added)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
|
||||
namespace lms::scanner
|
||||
{
|
||||
@@ -40,6 +41,7 @@ namespace lms::scanner
|
||||
removeOrphanedLabels(context);
|
||||
removeOrphanedCountries(context);
|
||||
removeOrphanedDirectories(context);
|
||||
removeOrphanedTrackEmbeddedImages(context);
|
||||
}
|
||||
|
||||
void ScanStepRemoveOrphanedDbEntries::removeOrphanedClusters(ScanContext& context)
|
||||
@@ -90,6 +92,12 @@ namespace lms::scanner
|
||||
removeOrphanedEntries<db::Directory>(context);
|
||||
}
|
||||
|
||||
void ScanStepRemoveOrphanedDbEntries::removeOrphanedTrackEmbeddedImages(ScanContext& context)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Checking orphaned embedded images in tracks...");
|
||||
removeOrphanedEntries<db::TrackEmbeddedImage>(context);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ScanStepRemoveOrphanedDbEntries::removeOrphanedEntries(ScanContext& context)
|
||||
{
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace lms::scanner
|
||||
void removeOrphanedLabels(ScanContext& context);
|
||||
void removeOrphanedCountries(ScanContext& context);
|
||||
void removeOrphanedDirectories(ScanContext& context);
|
||||
void removeOrphanedTrackEmbeddedImages(ScanContext& context);
|
||||
|
||||
template<typename T>
|
||||
void removeOrphanedEntries(ScanContext& context);
|
||||
|
||||
@@ -19,32 +19,46 @@
|
||||
|
||||
#include "CoverArtId.hpp"
|
||||
|
||||
#include "SubsonicId.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "database/ImageId.hpp"
|
||||
#include "database/TrackEmbeddedImageId.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr char timestampSeparatorChar{ ':' };
|
||||
}
|
||||
constexpr char timestampSeparatorChar{ '-' };
|
||||
|
||||
std::string idToString(db::ImageId id)
|
||||
{
|
||||
return "im-" + id.toString();
|
||||
}
|
||||
std::string idToString(db::ImageId id)
|
||||
{
|
||||
return "im-" + id.toString();
|
||||
}
|
||||
|
||||
std::string idToString(db::TrackEmbeddedImageId id)
|
||||
{
|
||||
return "trim-" + id.toString();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string idToString(CoverArtId coverId)
|
||||
{
|
||||
// produce "id:timestamp"
|
||||
std::string res{ std::visit([](auto&& id) {
|
||||
return idToString(id);
|
||||
},
|
||||
coverId.id) };
|
||||
std::string res;
|
||||
|
||||
res += timestampSeparatorChar;
|
||||
res += std::to_string(coverId.timestamp);
|
||||
// produce "type-id[-timestamp]"
|
||||
if (db::TrackEmbeddedImageId * imageId{ std::get_if<db::TrackEmbeddedImageId>(&coverId.id) })
|
||||
{
|
||||
res = idToString(*imageId);
|
||||
assert(!coverId.timestamp.has_value());
|
||||
}
|
||||
else if (db::ImageId * imageId{ std::get_if<db::ImageId>(&coverId.id) })
|
||||
{
|
||||
res = idToString(*imageId);
|
||||
res += timestampSeparatorChar;
|
||||
assert(coverId.timestamp.has_value());
|
||||
res += std::to_string(*coverId.timestamp);
|
||||
}
|
||||
|
||||
assert(!res.empty());
|
||||
return res;
|
||||
}
|
||||
} // namespace lms::api::subsonic
|
||||
@@ -52,46 +66,37 @@ namespace lms::api::subsonic
|
||||
// Used to parse parameters
|
||||
namespace lms::core::stringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<db::ImageId> readAs(std::string_view str)
|
||||
{
|
||||
std::vector<std::string_view> values{ core::stringUtils::splitString(str, '-') };
|
||||
if (values.size() != 2)
|
||||
return std::nullopt;
|
||||
|
||||
if (values[0] != "im")
|
||||
return std::nullopt;
|
||||
|
||||
if (const auto value{ core::stringUtils::readAs<db::ReleaseId::ValueType>(values[1]) })
|
||||
return db::ImageId{ *value };
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::optional<api::subsonic::CoverArtId> readAs(std::string_view str)
|
||||
{
|
||||
// expect "id:timestamp"
|
||||
auto timeStampSeparator{ str.find_last_of(api::subsonic::timestampSeparatorChar) };
|
||||
if (timeStampSeparator == std::string_view::npos)
|
||||
std::optional<api::subsonic::CoverArtId> res;
|
||||
|
||||
std::vector<std::string_view> values{ core::stringUtils::splitString(str, '-') };
|
||||
if (values.size() <= 1)
|
||||
return std::nullopt;
|
||||
|
||||
std::string_view strId{ str.substr(0, timeStampSeparator) };
|
||||
std::string_view strTimestamp{ str.substr(timeStampSeparator + 1) };
|
||||
if (values[0] == "trim")
|
||||
{
|
||||
// expect "trim-id"
|
||||
if (values.size() == 2)
|
||||
{
|
||||
if (const auto value{ core::stringUtils::readAs<db::TrackEmbeddedImageId::ValueType>(values[1]) })
|
||||
res.emplace(db::TrackEmbeddedImageId{ *value });
|
||||
}
|
||||
}
|
||||
else if (values[0] == "im")
|
||||
{
|
||||
// expect "im-id-timestamp"
|
||||
if (values.size() == 3)
|
||||
{
|
||||
const auto imageId{ core::stringUtils::readAs<db::ImageId::ValueType>(values[1]) };
|
||||
const auto timestamp{ core::stringUtils::readAs<std::time_t>(values[2]) };
|
||||
|
||||
api::subsonic::CoverArtId cover;
|
||||
if (const auto imagetId{ readAs<db::ImageId>(strId) })
|
||||
cover.id = *imagetId;
|
||||
else if (const auto trackId{ readAs<db::TrackId>(strId) })
|
||||
cover.id = *trackId;
|
||||
else
|
||||
return std::nullopt;
|
||||
if (imageId && timestamp)
|
||||
res.emplace(db::ImageId{ *imageId }, *timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto timestamp{ readAs<std::time_t>(strTimestamp) })
|
||||
cover.timestamp = *timestamp;
|
||||
else
|
||||
return std::nullopt;
|
||||
|
||||
return cover;
|
||||
return res;
|
||||
}
|
||||
} // namespace lms::core::stringUtils
|
||||
|
||||
@@ -24,18 +24,23 @@
|
||||
|
||||
#include "core/String.hpp"
|
||||
#include "database/ImageId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "database/TrackEmbeddedImageId.hpp"
|
||||
|
||||
namespace lms::api::subsonic
|
||||
{
|
||||
struct CoverArtId
|
||||
{
|
||||
std::variant<db::ImageId, db::TrackId> id;
|
||||
std::time_t timestamp;
|
||||
std::variant<db::ImageId, db::TrackEmbeddedImageId> id;
|
||||
std::optional<std::time_t> timestamp;
|
||||
|
||||
CoverArtId(db::ImageId _id, std::time_t _timestamp)
|
||||
: id{ _id }
|
||||
, timestamp{ _timestamp } {}
|
||||
CoverArtId(db::TrackEmbeddedImageId _id)
|
||||
: id{ _id } {}
|
||||
};
|
||||
|
||||
std::string idToString(CoverArtId coverId);
|
||||
std::string idToString(db::ImageId imageId);
|
||||
} // namespace lms::api::subsonic
|
||||
|
||||
// Used to parse parameters
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "core/Utils.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackEmbeddedImageId.hpp"
|
||||
#include "database/TrackLyrics.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "services/artwork/IArtworkService.hpp"
|
||||
@@ -336,8 +337,8 @@ namespace lms::api::subsonic
|
||||
*size = core::utils::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 });
|
||||
|
||||
std::shared_ptr<image::IEncodedImage> image;
|
||||
if (const db::TrackId * trackId{ std::get_if<db::TrackId>(&coverArtId.id) })
|
||||
image = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, size);
|
||||
if (const db::TrackEmbeddedImageId * trackEmbeddedImageId{ std::get_if<db::TrackEmbeddedImageId>(&coverArtId.id) })
|
||||
image = core::Service<cover::IArtworkService>::get()->getTrackEmbeddedImage(*trackEmbeddedImageId, size);
|
||||
else if (const db::ImageId * imageId{ std::get_if<db::ImageId>(&coverArtId.id) })
|
||||
image = core::Service<cover::IArtworkService>::get()->getImage(*imageId, size);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "database/Image.hpp"
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
@@ -93,13 +94,14 @@ namespace lms::api::subsonic
|
||||
}
|
||||
else
|
||||
{
|
||||
db::Track::FindParameters params;
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setRelease(release->getId());
|
||||
params.setHasEmbeddedImage(true);
|
||||
params.setIsPreferred(true);
|
||||
params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontCoverAndSize);
|
||||
params.setRange(db::Range{ 0, 1 });
|
||||
|
||||
db::Track::find(context.dbSession, params, [&](const db::Track::pointer& track) {
|
||||
const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() };
|
||||
db::TrackEmbeddedImage::find(context.dbSession, params, [&](const db::TrackEmbeddedImage::pointer& image) {
|
||||
const CoverArtId coverArtId{ image->getId() };
|
||||
albumNode.setAttribute("coverArt", idToString(coverArtId));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "Playlist.hpp"
|
||||
|
||||
#include "core/String.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/TrackList.hpp"
|
||||
#include "database/User.hpp"
|
||||
|
||||
@@ -43,14 +43,14 @@ namespace lms::api::subsonic
|
||||
if (const db::User::pointer user{ tracklist->getUser() })
|
||||
playlistNode.setAttribute("owner", user->getLoginName());
|
||||
|
||||
db::Track::FindParameters params;
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setTrackList(tracklist->getId());
|
||||
params.setHasEmbeddedImage(true);
|
||||
params.setRange(db::Range{ 0, 1 });
|
||||
params.setSortMethod(db::TrackSortMethod::TrackList);
|
||||
params.setIsPreferred(true);
|
||||
params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontCoverAndSize);
|
||||
params.setRange(db::Range{ .offset = 0, .size = 1 });
|
||||
|
||||
db::Track::find(session, params, [&](const db::Track::pointer& track) {
|
||||
const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() };
|
||||
db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) {
|
||||
const CoverArtId coverArtId{ image->getId() };
|
||||
playlistNode.setAttribute("coverArt", idToString(coverArtId));
|
||||
});
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackArtistLink.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "services/feedback/IFeedbackService.hpp"
|
||||
@@ -110,17 +111,25 @@ namespace lms::api::subsonic
|
||||
|
||||
const Release::pointer release{ track->getRelease() };
|
||||
|
||||
if (track->hasCover())
|
||||
{
|
||||
const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() };
|
||||
trackResponse.setAttribute("coverArt", idToString(coverArtId));
|
||||
}
|
||||
else if (release)
|
||||
{
|
||||
if (const db::Image::pointer image{ release->getImage() })
|
||||
{
|
||||
const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() };
|
||||
TrackEmbeddedImage::FindParameters params;
|
||||
params.setTrack(track->getId());
|
||||
params.setIsPreferred(true);
|
||||
params.setRange(Range{ .offset = 0, .size = 1 });
|
||||
|
||||
bool hasEmbeddedImage{};
|
||||
TrackEmbeddedImage::find(context.dbSession, params, [&](const TrackEmbeddedImage::pointer& image) {
|
||||
const CoverArtId coverArtId{ image->getId() };
|
||||
trackResponse.setAttribute("coverArt", idToString(coverArtId));
|
||||
});
|
||||
|
||||
if (!hasEmbeddedImage && release)
|
||||
{
|
||||
if (const db::Image::pointer image{ release->getImage() })
|
||||
{
|
||||
const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() };
|
||||
trackResponse.setAttribute("coverArt", idToString(coverArtId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -253,8 +253,8 @@ namespace lms::ui
|
||||
<< " artist: \"" << (!artists.empty() ? core::stringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\","
|
||||
<< " release: \"" << (track->getRelease() ? core::stringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
|
||||
<< " artwork: ["
|
||||
<< " { src: \"" << LmsApp->getArtworkResource()->getTrackImageUrl(trackId, ArtworkResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" },"
|
||||
<< " { src: \"" << LmsApp->getArtworkResource()->getTrackImageUrl(trackId, ArtworkResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" },"
|
||||
<< " { src: \"" << LmsApp->getArtworkResource()->getPreferredTrackImageUrl(trackId, ArtworkResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" },"
|
||||
<< " { src: \"" << LmsApp->getArtworkResource()->getPreferredTrackImageUrl(trackId, ArtworkResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" },"
|
||||
<< " ]"
|
||||
<< "};";
|
||||
// Update 'sizes' above to match this:
|
||||
|
||||
@@ -84,7 +84,7 @@ namespace lms::ui::utils
|
||||
std::unique_ptr<Wt::WImage> createTrackImage(db::TrackId trackId, ArtworkResource::Size size)
|
||||
{
|
||||
auto cover{ std::make_unique<Wt::WImage>() };
|
||||
cover->setImageLink(LmsApp->getArtworkResource()->getTrackImageUrl(trackId, size));
|
||||
cover->setImageLink(LmsApp->getArtworkResource()->getPreferredTrackImageUrl(trackId, size));
|
||||
cover->setStyleClass("Lms-cover img-fluid"); // HACK
|
||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/TrackEmbeddedImage.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "services/artwork/IArtworkService.hpp"
|
||||
|
||||
#include "LmsApplication.hpp"
|
||||
@@ -89,13 +91,14 @@ namespace lms::ui
|
||||
}
|
||||
else
|
||||
{
|
||||
db::Track::FindParameters params;
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setRelease(releaseId);
|
||||
params.setHasEmbeddedImage(true);
|
||||
params.setIsPreferred(true);
|
||||
params.setSortMethod(db::TrackEmbeddedImageSortMethod::FrontCoverAndSize);
|
||||
params.setRange(db::Range{ 0, 1 });
|
||||
|
||||
db::Track::find(LmsApp->getDbSession(), params, [&](const db::Track::pointer& track) {
|
||||
url = getImageUrl(track->getId(), size, "release");
|
||||
db::TrackEmbeddedImage::find(LmsApp->getDbSession(), params, [&](const db::TrackEmbeddedImage::pointer& image) {
|
||||
url = getImageUrl(image->getId(), size, "release");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -107,24 +110,32 @@ namespace lms::ui
|
||||
return url;
|
||||
}
|
||||
|
||||
std::string ArtworkResource::getTrackImageUrl(db::TrackId trackId, std::optional<Size> size) const
|
||||
std::string ArtworkResource::getPreferredTrackImageUrl(db::TrackId trackId, std::optional<Size> size) const
|
||||
{
|
||||
std::string url;
|
||||
|
||||
{
|
||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||
|
||||
const db::Track::pointer track{ db::Track::find(LmsApp->getDbSession(), trackId) };
|
||||
if (track)
|
||||
db::TrackEmbeddedImage::FindParameters params;
|
||||
params.setTrack(trackId);
|
||||
params.setIsPreferred(true);
|
||||
params.setRange(db::Range{ .offset = 0, .size = 1 });
|
||||
|
||||
db::TrackEmbeddedImage::find(LmsApp->getDbSession(), params, [&](const db::TrackEmbeddedImage::pointer& image) {
|
||||
url = getImageUrl(image->getId(), size, "release");
|
||||
});
|
||||
|
||||
if (url.empty())
|
||||
{
|
||||
if (track->hasCover())
|
||||
db::Track::pointer track{ db::Track::find(LmsApp->getDbSession(), trackId) };
|
||||
if (track)
|
||||
{
|
||||
url = getImageUrl(trackId, size, "release");
|
||||
}
|
||||
else if (const db::Release::pointer release{ track->getRelease() })
|
||||
{
|
||||
if (const db::Image::pointer image{ release->getImage() })
|
||||
url = getImageUrl(image->getId(), size, "release");
|
||||
if (const db::Release::pointer release{ track->getRelease() })
|
||||
{
|
||||
if (const db::Image::pointer image{ release->getImage() })
|
||||
url = getImageUrl(image->getId(), size, "release");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,9 +155,9 @@ namespace lms::ui
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string ArtworkResource::getImageUrl(db::TrackId trackId, std::optional<Size> size, std::string_view type) const
|
||||
std::string ArtworkResource::getImageUrl(db::TrackEmbeddedImageId trackId, std::optional<Size> size, std::string_view type) const
|
||||
{
|
||||
std::string res{ url() + "&trackid=" + trackId.toString() + "&type=" + std::string{ type } };
|
||||
std::string res{ url() + "&trimageid=" + trackId.toString() + "&type=" + std::string{ type } };
|
||||
if (size)
|
||||
res += "&size=" + std::to_string(static_cast<std::size_t>(*size));
|
||||
return res;
|
||||
@@ -168,13 +179,13 @@ namespace lms::ui
|
||||
|
||||
// Retrieve parameters
|
||||
const std::string* imageIdStr = request.getParameter("imageid");
|
||||
const std::string* trackIdStr = request.getParameter("trackid");
|
||||
const std::string* trackEmbeddedImageIdStr = request.getParameter("trimageid");
|
||||
const std::string* sizeStr = request.getParameter("size");
|
||||
const std::string* typeStr = request.getParameter("type");
|
||||
|
||||
std::shared_ptr<image::IEncodedImage> image;
|
||||
|
||||
if ((imageIdStr || trackIdStr))
|
||||
if ((imageIdStr || trackEmbeddedImageIdStr))
|
||||
{
|
||||
const auto size{ sizeStr ? core::stringUtils::readAs<std::size_t>(*sizeStr) : std::nullopt };
|
||||
if (size && *size > maxSize)
|
||||
@@ -185,22 +196,13 @@ namespace lms::ui
|
||||
|
||||
if (imageIdStr)
|
||||
{
|
||||
const std::optional<db::ImageId> imageId{ core::stringUtils::readAs<db::ImageId::ValueType>(*imageIdStr) };
|
||||
if (!imageId)
|
||||
return;
|
||||
|
||||
image = core::Service<cover::IArtworkService>::get()->getImage(*imageId, size);
|
||||
if (const auto imageId{ core::stringUtils::readAs<db::ImageId::ValueType>(*imageIdStr) })
|
||||
image = core::Service<cover::IArtworkService>::get()->getImage(*imageId, size);
|
||||
}
|
||||
else if (trackIdStr)
|
||||
else if (trackEmbeddedImageIdStr)
|
||||
{
|
||||
const std::optional<db::TrackId> trackId{ core::stringUtils::readAs<db::TrackId::ValueType>(*trackIdStr) };
|
||||
if (!trackId)
|
||||
{
|
||||
LOG(DEBUG, "track not found");
|
||||
return;
|
||||
}
|
||||
|
||||
image = core::Service<cover::IArtworkService>::get()->getTrackImage(*trackId, size);
|
||||
if (const auto imageId{ core::stringUtils::readAs<db::TrackEmbeddedImageId::ValueType>(*trackEmbeddedImageIdStr) })
|
||||
image = core::Service<cover::IArtworkService>::get()->getTrackEmbeddedImage(*imageId, size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "database/ArtistId.hpp"
|
||||
#include "database/ImageId.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/TrackEmbeddedImageId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
|
||||
namespace lms::ui
|
||||
@@ -46,11 +47,11 @@ namespace lms::ui
|
||||
|
||||
std::string getArtistImageUrl(db::ArtistId artistId, std::optional<Size> size = std::nullopt) const;
|
||||
std::string getReleaseCoverUrl(db::ReleaseId releaseId, std::optional<Size> size = std::nullopt) const;
|
||||
std::string getTrackImageUrl(db::TrackId trackId, std::optional<Size> size = std::nullopt) const;
|
||||
std::string getPreferredTrackImageUrl(db::TrackId trackId, std::optional<Size> size = std::nullopt) const;
|
||||
|
||||
private:
|
||||
std::string getImageUrl(db::ImageId imageId, std::optional<Size> size, std::string_view type) const;
|
||||
std::string getImageUrl(db::TrackId trackId, std::optional<Size> size, std::string_view type) const;
|
||||
std::string getImageUrl(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional<Size> size, std::string_view type) const;
|
||||
|
||||
std::string getDefaultArtistImageUrl() const;
|
||||
std::string getDefaultReleaseCoverUrl() const;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
add_subdirectory(db-generator)
|
||||
add_subdirectory(cover)
|
||||
add_subdirectory(metadata)
|
||||
add_subdirectory(recommendation)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
add_executable(lms-cover
|
||||
LmsCover.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(lms-cover PRIVATE
|
||||
lmsartwork
|
||||
Boost::program_options
|
||||
)
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/StreamLogger.hpp"
|
||||
#include "core/SystemPaths.hpp"
|
||||
#include "database/Db.hpp"
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "image/Image.hpp"
|
||||
#include "services/artwork/IArtworkService.hpp"
|
||||
|
||||
namespace lms
|
||||
{
|
||||
void dumpTrackCovers(db::Session& session, image::ImageSize width)
|
||||
{
|
||||
using namespace db;
|
||||
|
||||
RangeResults<db::TrackId> trackIds;
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
trackIds = db::Track::findIds(session, db::Track::FindParameters{});
|
||||
}
|
||||
|
||||
for (const db::TrackId trackId : trackIds.results)
|
||||
{
|
||||
std::cout << "Getting cover for track id " << trackId.toString() << std::endl;
|
||||
core::Service<cover::IArtworkService>::get()->getTrackImage(trackId, width);
|
||||
}
|
||||
}
|
||||
} // namespace lms
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
try
|
||||
{
|
||||
using namespace lms;
|
||||
namespace po = boost::program_options;
|
||||
|
||||
// log to stdout
|
||||
core::Service<core::logging::ILogger> logger{ std::make_unique<core::logging::StreamLogger>(std::cout) };
|
||||
|
||||
po::options_description desc{ "Allowed options" };
|
||||
|
||||
// clang-format off
|
||||
desc.add_options()
|
||||
("help,h", "print usage message")
|
||||
("conf,c", po::value<std::string>()->default_value(core::sysconfDirectory / "lms.conf"), "LMS config file")
|
||||
("default-release-cover,d", po::value<std::string>(), "Default release cover path")
|
||||
("default-artist-image,d", po::value<std::string>(), "Default artist image")
|
||||
("tracks,t", "dump covers for tracks")
|
||||
("size,s", po::value<unsigned>()->default_value(512), "Requested cover size")
|
||||
("quality,q", po::value<unsigned>()->default_value(75), "JPEG quality (1-100)");
|
||||
// clang-format on
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
|
||||
if (vm.count("help"))
|
||||
{
|
||||
std::cout << desc << std::endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
image::init(argv[0]);
|
||||
core::Service<core::IConfig> config{ core::createConfig(vm["conf"].as<std::string>()) };
|
||||
db::Db db{ config->getPath("working-dir", "/var/lms") / "lms.db" };
|
||||
core::Service<cover::IArtworkService> coverArtService{ cover::createArtworkService(db, vm["default-release-cover,"].as<std::string>(), vm["default-artist-image"].as<std::string>()) };
|
||||
|
||||
coverArtService->setJpegQuality(config->getULong("cover-jpeg-quality", vm["quality"].as<unsigned>()));
|
||||
|
||||
db::Session session{ db };
|
||||
|
||||
if (vm.count("tracks"))
|
||||
dumpTrackCovers(session, vm["size"].as<unsigned>());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
std::cerr << "Caught exception: " << e.what() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user