2
2
* Copyright (C) 2013 Canonical Ltd.
4
4
* This program is free software: you can redistribute it and/or modify
5
* it under the terms of the GNU Lesser General Public License version 3 as
5
* it under the terms of the GNU General Public License version 3 as
6
6
* published by the Free Software Foundation.
8
8
* This program is distributed in the hope that it will be useful,
9
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU Lesser General Public License for more details.
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
13
* You should have received a copy of the GNU General Public License
14
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
* Authored by: Jussi Pakkanen <jussi.pakkanen@canonical.com>
21
21
#include<internal/audioimageextractor.h>
22
22
#include<internal/videoscreenshotter.h>
23
23
#include<internal/imagescaler.h>
24
#include<internal/mediaartcache.h>
25
#include<internal/lastfmdownloader.h>
26
#include<internal/ubuntuserverdownloader.h>
27
#include<gdk-pixbuf/gdk-pixbuf.h>
30
25
#include<gst/gst.h>
31
26
#include<stdexcept>
40
28
using namespace std;
30
class GstInitializer {
32
GstInitializer() { gst_init(nullptr, nullptr); };
42
35
class ThumbnailerPrivate {
46
string create_audio_thumbnail(const string &abspath, ThumbnailSize desired_size,
47
ThumbnailPolicy policy);
48
string create_video_thumbnail(const string &abspath, ThumbnailSize desired_size);
49
string create_generic_thumbnail(const string &abspath, ThumbnailSize desired_size,
50
const string &original_location="");
51
string detect_standalone_thumbnail(const string &abspath, ThumbnailSize desired_size);
54
37
ThumbnailCache cache;
55
38
AudioImageExtractor audio;
56
39
VideoScreenshotter video;
57
40
ImageScaler scaler;
58
MediaArtCache macache;
59
std::unique_ptr<ArtDownloader> downloader;
61
ThumbnailerPrivate() {
62
char *artservice = getenv("THUMBNAILER_ART_PROVIDER");
63
if (artservice != nullptr && strcmp(artservice, "lastfm") == 0)
65
downloader.reset(new LastFMDownloader());
69
downloader.reset(new UbuntuServerDownloader());
73
string create_thumbnail(const string &abspath, ThumbnailSize desired_size,
74
ThumbnailPolicy policy);
75
string create_random_filename();
42
ThumbnailerPrivate() {};
44
string create_thumbnail(const string &abspath, ThumbnailSize desired_size);
79
string ThumbnailerPrivate::create_random_filename() {
81
char *dirbase = getenv("TMPDIR"); // Set when in a confined application.
87
fname += "/thumbnailer.";
88
fname += to_string(rnd());
93
string ThumbnailerPrivate::detect_standalone_thumbnail(const string &abspath, ThumbnailSize desired_size) {
94
static const std::array<const char *, 2> suffixes{"png", "jpg"};
95
static const std::array<const char *, 6> namebases{"cover", "album", "albumart", "front", ".folder", "folder"};
96
auto slash = abspath.rfind('/');
97
if (slash == string::npos) {
100
string dirname = abspath.substr(0, slash);
102
unique_ptr<DIR, int(*)(DIR*)> dir(opendir(dirname.c_str()), closedir);
103
unique_ptr<struct dirent, void(*)(void*)> entry((dirent*)malloc(sizeof(dirent) + NAME_MAX + 1), free);
104
struct dirent *de = nullptr;
105
while(readdir_r(dir.get(), entry.get(), &de) == 0 && de) {
106
const string fname(entry->d_name);
107
auto sufpoint = fname.rfind('.');
108
if(sufpoint == string::npos) {
111
auto namebase = fname.substr(0, sufpoint);
112
auto suffix = fname.substr(sufpoint+1);
113
transform(namebase.begin(), namebase.end(), namebase.begin(), ::tolower);
114
transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower);
115
if(find(namebases.begin(), namebases.end(), namebase) != namebases.end() &&
116
find(suffixes.begin(), suffixes.end(), suffix) != suffixes.end()) {
117
detected = dirname + "/" + fname;
120
if(detected.empty()) {
123
return create_generic_thumbnail(detected, desired_size, abspath);
126
string ThumbnailerPrivate::create_audio_thumbnail(const string &abspath,
127
ThumbnailSize desired_size, ThumbnailPolicy /*policy*/) {
47
string ThumbnailerPrivate::create_thumbnail(const string &abspath, ThumbnailSize desired_size) {
48
// We do not know what the file pointed to is. File suffixes may be missing
49
// or just plain lie. So we try to open it one by one with the handlers we have.
50
// Go from least resource intensive to most.
128
51
string tnfile = cache.get_cache_file_name(abspath, desired_size);
129
string tmpname = create_random_filename();
52
char filebuf[] = "/tmp/some/long/text/here/so/path/will/fit";
53
string tmpname = tmpnam(filebuf);
55
if(scaler.scale(abspath, tnfile, desired_size))
57
} catch(runtime_error &e) {
58
// Fail is ok, just try the next one.
130
60
bool extracted = false;
132
62
if(audio.extract(abspath, tmpname)) {
135
65
} catch(runtime_error &e) {
138
scaler.scale(tmpname, tnfile, desired_size, abspath); // If this throws, let it propagate.
68
scaler.scale(tmpname, tnfile, desired_size); // If this throws, let it propagate.
139
69
unlink(tmpname.c_str());
142
return detect_standalone_thumbnail(abspath, desired_size);
145
string ThumbnailerPrivate::create_generic_thumbnail(const string &abspath, ThumbnailSize desired_size,
146
const string &orig_loc) {
148
const string &original_location = orig_loc.empty() ? abspath : orig_loc;
149
string tnfile = cache.get_cache_file_name(original_location, desired_size);
150
// Special case: full size image files are their own preview.
151
if(desired_size == TN_SIZE_ORIGINAL &&
152
gdk_pixbuf_get_file_info(abspath.c_str(), &tmpw, &tmph)) {
156
if(scaler.scale(abspath, tnfile, desired_size, original_location))
158
} catch(const runtime_error &e) {
159
fprintf(stderr, "Scaling thumbnail failed: %s\n", e.what());
73
if(video.extract(abspath, tmpname)) {
76
} catch(runtime_error &e) {
79
scaler.scale(tmpname, tnfile, desired_size); // If this throws, let it propagate.
80
unlink(tmpname.c_str());
83
// None of our handlers knew how to handle it.
164
string ThumbnailerPrivate::create_video_thumbnail(const string &abspath, ThumbnailSize desired_size) {
165
string tnfile = cache.get_cache_file_name(abspath, desired_size);
166
string tmpname = create_random_filename();
167
if(video.extract(abspath, tmpname)) {
168
scaler.scale(tmpname, tnfile, desired_size, abspath);
169
unlink(tmpname.c_str());
172
throw runtime_error("Video extraction failed.");
175
string ThumbnailerPrivate::create_thumbnail(const string &abspath, ThumbnailSize desired_size,
176
ThumbnailPolicy policy) {
177
// Every now and then see if we have too much stuff and delete them if so.
178
if((rnd() % 100) == 0) { // No, this is not perfectly random. It does not need to be.
181
std::unique_ptr<GFile, void(*)(void *)> file(
182
g_file_new_for_path(abspath.c_str()), g_object_unref);
187
std::unique_ptr<GFileInfo, void(*)(void *)> info(
188
g_file_query_info(file.get(), G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
189
G_FILE_QUERY_INFO_NONE, /* cancellable */ NULL, /* error */NULL),
195
std::string content_type(g_file_info_get_content_type(info.get()));
196
if (content_type.empty()) {
200
if (content_type.find("audio/") == 0) {
201
return create_audio_thumbnail(abspath, desired_size, policy);
204
if (content_type.find("video/") == 0) {
205
return create_video_thumbnail(abspath, desired_size);
208
return create_generic_thumbnail(abspath, desired_size);
211
87
Thumbnailer::Thumbnailer() {
88
static GstInitializer i; // C++ standard guarantees this to be lazy and thread safe.
212
89
p = new ThumbnailerPrivate();
215
92
Thumbnailer::~Thumbnailer() {
218
std::string Thumbnailer::get_thumbnail(const std::string &filename, ThumbnailSize desired_size,
219
ThumbnailPolicy policy) {
96
string Thumbnailer::get_thumbnail(const string &filename, ThumbnailSize desired_size) {
221
98
if(filename[0] != '/') {
222
99
abspath += getcwd(nullptr, 0);
227
104
std::string estimate = p->cache.get_if_exists(abspath, desired_size);
228
105
if(!estimate.empty())
230
return p->create_thumbnail(abspath, desired_size, policy);
233
string Thumbnailer::get_thumbnail(const string &filename, ThumbnailSize desired_size) {
234
return get_thumbnail(filename, desired_size, TN_LOCAL);
237
unique_ptr<gchar, void(*)(gpointer)> get_art_file_content(const string &fname, gsize &content_size)
240
GError *err = nullptr;
241
if(!g_file_get_contents(fname.c_str(), &contents, &content_size, &err)) {
242
unlink(fname.c_str());
243
std::string msg("Error reading file: ");
246
throw std::runtime_error(msg);
248
unlink(fname.c_str());
249
return unique_ptr<gchar, void(*)(gpointer)>(contents, g_free);
252
std::string Thumbnailer::get_album_art(const std::string &artist, const std::string &album,
253
ThumbnailSize desired_size, ThumbnailPolicy policy) {
254
if(!p->macache.has_album_art(artist, album)) {
255
if(policy == TN_LOCAL) {
256
// We don't have it cached and can't access the net
257
// -> nothing to be done.
260
char filebuf[] = "/tmp/some/long/text/here/so/path/will/fit";
261
std::string tmpname = tmpnam(filebuf);
262
if(!p->downloader->download(artist, album, tmpname)) {
267
auto contents = get_art_file_content(tmpname, content_size);
268
p->macache.add_album_art(artist, album, contents.get(), content_size);
270
// At this point we know we have the image in our art cache (unless
271
// someone just deleted it concurrently, in which case we can't
272
// really do anything.
273
std::string original = p->macache.get_album_art_file(artist, album);
274
if(desired_size == TN_SIZE_ORIGINAL) {
277
return get_thumbnail(original, desired_size, policy);
280
std::string Thumbnailer::get_artist_art(const std::string &artist, const std::string &album, ThumbnailSize desired_size,
281
ThumbnailPolicy policy) {
282
if(!p->macache.has_artist_art(artist, album)) {
283
if(policy == TN_LOCAL) {
284
// We don't have it cached and can't access the net
285
// -> nothing to be done.
288
char filebuf[] = "/tmp/some/long/text/here/so/path/will/fit";
289
std::string tmpname = tmpnam(filebuf);
290
if(!p->downloader->download_artist(artist, album, tmpname)) {
294
auto contents = get_art_file_content(tmpname, content_size);
295
p->macache.add_artist_art(artist, album, contents.get(), content_size);
297
// At this point we know we have the image in our art cache (unless
298
// someone just deleted it concurrently, in which case we can't
299
// really do anything.
300
std::string original = p->macache.get_artist_art_file(artist, album);
301
if(desired_size == TN_SIZE_ORIGINAL) {
304
return get_thumbnail(original, desired_size, policy);
107
p->create_thumbnail(abspath, desired_size);
108
return p->cache.get_if_exists(abspath, desired_size);