/* * 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 #include "logger/Logger.hpp" #include "av/InputFormatContext.hpp" #include "CoverArtGrabber.hpp" namespace { bool isFileSupported(const boost::filesystem::path& file, const std::vector extensions) { boost::filesystem::path fileExtension = file.extension(); for (auto extension : extensions) { if (extension == fileExtension) return true; } return false; } } // namespace namespace CoverArt { Grabber::Grabber() : _maxFileSize(0) { } Grabber& Grabber::instance() { static Grabber instance; return instance; } void Grabber::init(const Config& config) { for (auto extension : config.fileExtensions) _fileExtensions.push_back("." + extension); _maxFileSize = config.maxFileSize; } std::vector Grabber::getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers) const { std::vector res; try { std::vector pictures = input.getPictures(nbMaxCovers); BOOST_FOREACH(const Av::Picture& picture, pictures) res.push_back( CoverArt(picture.mimeType, picture.data) ); } catch(std::exception& e) { LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get pictures: " << e.what(); } return res; } std::vector Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCovers) const { std::vector res; std::vector coverPathes = getCoverPaths(p, nbMaxCovers); for (auto coverPath : coverPathes) { if (res.size() >= nbMaxCovers) break; std::vector data; std::ifstream file(coverPath.string(), std::ios::binary); char c; while (file.get(c)) data.push_back(c); // TODO handle other formats res.push_back(CoverArt("image/jpeg", data)); } return res; } std::vector Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers) const { std::vector res; // TODO handle preferred file names boost::filesystem::directory_iterator itPath(directoryPath); boost::filesystem::directory_iterator itEnd; while (itPath != itEnd) { boost::filesystem::path path = *itPath; itPath++; if (!boost::filesystem::is_regular(path)) continue; if (!isFileSupported(path, _fileExtensions)) continue; if (boost::filesystem::file_size(path) > _maxFileSize) { LMS_LOG(MOD_COVER, SEV_INFO) << "Cover file '" << path << " is too big (" << boost::filesystem::file_size(path) << "), limit is " << _maxFileSize; continue; } res.push_back(path); if (res.size() >= nbMaxCovers) break; } return res; } std::vector Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const { std::vector res; try { Av::InputFormatContext input(p); res = getFromInputFormatContext(input, nbMaxCovers); } catch(std::exception& e) { LMS_LOG(MOD_COVER, SEV_ERROR) << "Cannot get covers from file " << p << ": " << e.what(); } return res; } std::vector Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers) const { using namespace Database; Wt::Dbo::Transaction transaction(session); Track::pointer track = Track::getById(session, trackId); if (!track) return std::vector(); Track::CoverType coverType = track->getCoverType(); boost::filesystem::path trackPath = track->getPath(); transaction.commit(); switch (coverType) { case Track::CoverType::Embedded: return Grabber::getFromTrack(trackPath, nbMaxCovers); case Track::CoverType::ExternalFile: return Grabber::getFromDirectory(trackPath.parent_path(), nbMaxCovers); case Track::CoverType::None: return std::vector(); } return std::vector(); } std::vector Grabber::getFromRelease(Wt::Dbo::Session& session, std::string releaseName, std::size_t nbMaxCovers) const { using namespace Database; boost::filesystem::path firstTrackPath; bool embeddedCover = false; // Get the first track of the release { SearchFilter filter; filter.exactMatch[SearchFilter::Field::Release].push_back(releaseName); Wt::Dbo::Transaction transaction(session); std::vector tracks = Track::getAll(session, filter, -1, 1 /* limit result size */); if (tracks.empty()) return std::vector(); firstTrackPath = tracks.front()->getPath(); embeddedCover = (tracks.front()->getCoverType() == Track::CoverType::Embedded); } // First, try to get covers from the directory of the release std::vector res = getFromDirectory( firstTrackPath.parent_path(), nbMaxCovers); // Fallback on the embedded cover of the first track if (res.empty() && embeddedCover) res = getFromTrack( firstTrackPath, nbMaxCovers); return res; } } // namespace CoverArt