~jpakkane/thumbnailer/standalone-albumart

« back to all changes in this revision

Viewing changes to src/thumbnailer.cpp

  • Committer: Jussi Pakkanen
  • Date: 2013-09-25 12:45:32 UTC
  • Revision ID: jussi.pakkanen@canonical.com-20130925124532-spx31kdvxwizl34a
Added Debian packaging.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 * Copyright (C) 2013 Canonical Ltd.
3
3
 *
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.
7
7
 *
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.
12
12
 *
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/>.
15
15
 *
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>
28
24
#include<unistd.h>
29
 
#include<cstring>
30
25
#include<gst/gst.h>
31
26
#include<stdexcept>
32
 
#include<random>
33
 
#include<gio/gio.h>
34
 
#include<glib.h>
35
 
#include<memory>
36
 
#include<dirent.h>
37
 
#include<sys/types.h>
38
 
#include<algorithm>
39
27
 
40
28
using namespace std;
41
29
 
 
30
class GstInitializer {
 
31
public:
 
32
    GstInitializer() { gst_init(nullptr, nullptr); };
 
33
};
 
34
 
42
35
class ThumbnailerPrivate {
43
 
private:
44
 
    random_device rnd;
45
 
 
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);
52
 
 
53
36
public:
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;
60
 
 
61
 
    ThumbnailerPrivate() {
62
 
        char *artservice = getenv("THUMBNAILER_ART_PROVIDER");
63
 
        if (artservice != nullptr && strcmp(artservice, "lastfm") == 0)
64
 
        {
65
 
            downloader.reset(new LastFMDownloader());
66
 
        }
67
 
        else
68
 
        {
69
 
            downloader.reset(new UbuntuServerDownloader());
70
 
        }
71
 
    };
72
 
 
73
 
    string create_thumbnail(const string &abspath, ThumbnailSize desired_size,
74
 
            ThumbnailPolicy policy);
75
 
    string create_random_filename();
 
41
 
 
42
    ThumbnailerPrivate() {};
 
43
 
 
44
    string create_thumbnail(const string &abspath, ThumbnailSize desired_size);
76
45
};
77
46
 
78
 
 
79
 
string ThumbnailerPrivate::create_random_filename() {
80
 
    string fname;
81
 
    char *dirbase = getenv("TMPDIR"); // Set when in a confined application.
82
 
    if(dirbase) {
83
 
        fname = dirbase;
84
 
    } else {
85
 
        fname = "/tmp";
86
 
    }
87
 
    fname += "/thumbnailer.";
88
 
    fname += to_string(rnd());
89
 
    fname += ".tmp";
90
 
    return fname;
91
 
}
92
 
 
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) {
98
 
        return "";
99
 
    }
100
 
    string dirname = abspath.substr(0, slash);
101
 
    string detected;
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) {
109
 
            continue;
110
 
        }
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;
118
 
        }
119
 
    }
120
 
    if(detected.empty()) {
121
 
        return "";
122
 
    }
123
 
    return create_generic_thumbnail(detected, desired_size, abspath);
124
 
}
125
 
 
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);
 
54
    try {
 
55
        if(scaler.scale(abspath, tnfile, desired_size))
 
56
            return tnfile;
 
57
    } catch(runtime_error &e) {
 
58
        // Fail is ok, just try the next one.
 
59
    }
130
60
    bool extracted = false;
131
61
    try {
132
62
        if(audio.extract(abspath, tmpname)) {
135
65
    } catch(runtime_error &e) {
136
66
    }
137
67
    if(extracted) {
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());
140
70
        return tnfile;
141
71
    }
142
 
    return detect_standalone_thumbnail(abspath, desired_size);
143
 
}
144
 
 
145
 
string ThumbnailerPrivate::create_generic_thumbnail(const string &abspath, ThumbnailSize desired_size,
146
 
        const string &orig_loc) {
147
 
    int tmpw, tmph;
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)) {
153
 
        return abspath;
154
 
    }
155
72
    try {
156
 
        if(scaler.scale(abspath, tnfile, desired_size, original_location))
157
 
            return tnfile;
158
 
    } catch(const runtime_error &e) {
159
 
        fprintf(stderr, "Scaling thumbnail failed: %s\n", e.what());
160
 
    }
 
73
        if(video.extract(abspath, tmpname)) {
 
74
            extracted = true;
 
75
        }
 
76
    } catch(runtime_error &e) {
 
77
    }
 
78
    if(extracted) {
 
79
        scaler.scale(tmpname, tnfile, desired_size); // If this throws, let it propagate.
 
80
        unlink(tmpname.c_str());
 
81
        return tnfile;
 
82
    }
 
83
    // None of our handlers knew how to handle it.
161
84
    return "";
162
85
}
163
86
 
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());
170
 
        return tnfile;
171
 
    }
172
 
    throw runtime_error("Video extraction failed.");
173
 
}
174
 
 
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.
179
 
        cache.prune();
180
 
    }
181
 
    std::unique_ptr<GFile, void(*)(void *)> file(
182
 
            g_file_new_for_path(abspath.c_str()), g_object_unref);
183
 
    if(!file) {
184
 
        return "";
185
 
    }
186
 
 
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),
190
 
            g_object_unref);
191
 
    if(!info) {
192
 
        return "";
193
 
    }
194
 
 
195
 
    std::string content_type(g_file_info_get_content_type(info.get()));
196
 
    if (content_type.empty()) {
197
 
        return "";
198
 
    }
199
 
 
200
 
    if (content_type.find("audio/") == 0) {
201
 
        return create_audio_thumbnail(abspath, desired_size, policy);
202
 
    }
203
 
 
204
 
    if (content_type.find("video/") == 0) {
205
 
        return create_video_thumbnail(abspath, desired_size);
206
 
    }
207
 
 
208
 
    return create_generic_thumbnail(abspath, desired_size);
209
 
}
210
 
 
211
87
Thumbnailer::Thumbnailer() {
 
88
    static GstInitializer i; // C++ standard guarantees this to be lazy and thread safe.
212
89
    p = new ThumbnailerPrivate();
213
90
}
214
91
 
215
92
Thumbnailer::~Thumbnailer() {
216
93
    delete p;
217
94
}
218
 
std::string Thumbnailer::get_thumbnail(const std::string &filename, ThumbnailSize desired_size,
219
 
        ThumbnailPolicy policy) {
 
95
 
 
96
string Thumbnailer::get_thumbnail(const string &filename, ThumbnailSize desired_size) {
220
97
    string abspath;
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())
229
106
        return estimate;
230
 
    return p->create_thumbnail(abspath, desired_size, policy);
231
 
}
232
 
 
233
 
string Thumbnailer::get_thumbnail(const string &filename, ThumbnailSize desired_size) {
234
 
    return get_thumbnail(filename, desired_size, TN_LOCAL);
235
 
}
236
 
 
237
 
unique_ptr<gchar, void(*)(gpointer)> get_art_file_content(const string &fname, gsize &content_size)
238
 
{
239
 
    gchar *contents;
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: ");
244
 
        msg += err->message;
245
 
        g_error_free(err);
246
 
        throw std::runtime_error(msg);
247
 
    }
248
 
    unlink(fname.c_str());
249
 
    return unique_ptr<gchar, void(*)(gpointer)>(contents, g_free);
250
 
}
251
 
 
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.
258
 
            return "";
259
 
        }
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)) {
263
 
            return "";
264
 
        }
265
 
 
266
 
        gsize content_size;
267
 
        auto contents = get_art_file_content(tmpname, content_size);
268
 
        p->macache.add_album_art(artist, album, contents.get(), content_size);
269
 
    }
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) {
275
 
        return original;
276
 
    }
277
 
    return get_thumbnail(original, desired_size, policy);
278
 
}
279
 
 
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.
286
 
            return "";
287
 
        }
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)) {
291
 
            return "";
292
 
        }
293
 
        gsize content_size;
294
 
        auto contents = get_art_file_content(tmpname, content_size);
295
 
        p->macache.add_artist_art(artist, album, contents.get(), content_size);
296
 
    }
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) {
302
 
        return original;
303
 
    }
304
 
    return get_thumbnail(original, desired_size, policy);
305
 
 
306
 
    return "";
 
107
    p->create_thumbnail(abspath, desired_size);
 
108
    return p->cache.get_if_exists(abspath, desired_size);
307
109
}