/* * Copyright (C) 2013 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 . */ #include "ArtworkService.hpp" #include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "core/Utils.hpp" #include "database/Artist.hpp" #include "database/Artwork.hpp" #include "database/Db.hpp" #include "database/Image.hpp" #include "database/ImageId.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "database/TrackEmbeddedImage.hpp" #include "database/TrackEmbeddedImageLink.hpp" #include "database/TrackList.hpp" #include "image/Exception.hpp" #include "image/IEncodedImage.hpp" #include "image/Image.hpp" #include "metadata/IAudioFileParser.hpp" namespace lms::artwork { std::unique_ptr createArtworkService(db::Db& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath) { return std::make_unique(db, defaultReleaseCoverSvgPath, defaultArtistImageSvgPath); } ArtworkService::ArtworkService(db::Db& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath) : _db{ db } , _audioFileParser{ metadata::createAudioFileParser(metadata::AudioFileParserParameters{}) } , _cache{ core::Service::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 } { setJpegQuality(core::Service::get()->getULong("cover-jpeg-quality", 75)); LMS_LOG(COVER, INFO, "Default release cover path = " << defaultReleaseCoverSvgPath); LMS_LOG(COVER, INFO, "Max cache size = " << _cache.getMaxCacheSize()); _defaultReleaseCover = image::readImage(defaultReleaseCoverSvgPath); // may throw _defaultArtistImage = image::readImage(defaultArtistImageSvgPath); // may throw } ArtworkService::~ArtworkService() = default; std::unique_ptr ArtworkService::getFromImageFile(const std::filesystem::path& p, std::optional width) const { std::unique_ptr image; try { if (!width) { image = image::readImage(p); } else { auto rawImage{ image::decodeImage(p) }; rawImage->resize(*width); image = image::encodeToJPEG(*rawImage, _jpegQuality); } } catch (const image::Exception& e) { LMS_LOG(COVER, ERROR, "Cannot read cover in file " << p << ": " << e.what()); } return image; } std::shared_ptr ArtworkService::getDefaultReleaseArtwork() { return _defaultReleaseCover; } std::shared_ptr ArtworkService::getDefaultArtistArtwork() { return _defaultArtistImage; } std::unique_ptr ArtworkService::getTrackImage(const std::filesystem::path& p, std::size_t index, std::optional width) const { std::unique_ptr image; try { std::size_t currentIndex{}; _audioFileParser->parseImages(p, [&](const metadata::Image& parsedImage) { if (currentIndex++ != index) return; try { if (!width) { image = image::readImage(parsedImage.data, parsedImage.mimeType); } else { auto rawImage{ image::decodeImage(parsedImage.data) }; rawImage->resize(*width); image = image::encodeToJPEG(*rawImage, _jpegQuality); } } catch (const image::Exception& e) { LMS_LOG(COVER, ERROR, "Cannot decode image from track " << p << ": " << e.what()); } }); } catch (const metadata::Exception& e) { LMS_LOG(COVER, ERROR, "Cannot parse images from track " << p << ": " << e.what()); } return image; } db::ArtworkId ArtworkService::findTrackListImage(db::TrackListId trackListId) { db::ArtworkId artworkId; // Iterate over all tracks and stop when we find an artwork db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; db::TrackList::pointer trackList{ db::TrackList::find(session, trackListId) }; if (!trackList) return artworkId; const auto entries{ trackList->getEntries(db::Range{ 0, 10 }) }; for (const auto& entry : entries.results) { const auto track{ entry->getTrack() }; if (track->getPreferredMediaArtworkId().isValid()) { artworkId = track->getPreferredMediaArtworkId(); break; // stop iteration } if (track->getPreferredArtworkId().isValid()) { artworkId = track->getPreferredArtworkId(); break; // stop iteration } } return artworkId; } std::shared_ptr ArtworkService::getImage(db::ArtworkId artworkId, std::optional width) { const ImageCache::EntryDesc cacheEntryDesc{ artworkId, width }; std::shared_ptr image{ _cache.getImage(cacheEntryDesc) }; if (image) return image; db::TrackEmbeddedImageId trackEmbeddedImageId; db::ImageId imageId; { db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; db::Artwork::pointer artwork{ db::Artwork::find(session, artworkId) }; if (artwork) { trackEmbeddedImageId = artwork->getTrackEmbeddedImageId(); imageId = artwork->getImageId(); } } if (trackEmbeddedImageId.isValid()) image = getTrackEmbeddedImage(trackEmbeddedImageId, width); else if (imageId.isValid()) image = getImage(imageId, width); if (image) _cache.addImage(cacheEntryDesc, image); return image; } std::shared_ptr ArtworkService::getImage(db::ImageId imageId, std::optional width) { std::filesystem::path imageFile; { db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; const db::Image::pointer image{ db::Image::find(session, imageId) }; if (!image) return nullptr; imageFile = image->getAbsoluteFilePath(); } return getFromImageFile(imageFile, width); } std::shared_ptr ArtworkService::getTrackEmbeddedImage(db::TrackEmbeddedImageId trackEmbeddedImageId, std::optional width) { std::shared_ptr image; { db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; // TODO: could be put outside transaction db::TrackEmbeddedImageLink::find(session, trackEmbeddedImageId, [&](const db::TrackEmbeddedImageLink::pointer& link) { if (!image) image = getTrackImage(link->getTrack()->getAbsoluteFilePath(), link->getIndex(), width); }); } return image; } void ArtworkService::flushCache() { _cache.flush(); } void ArtworkService::setJpegQuality(unsigned quality) { _jpegQuality = core::utils::clamp(quality, 1, 100); LMS_LOG(COVER, INFO, "JPEG export quality = " << _jpegQuality); } } // namespace lms::artwork