~stolowski/thumbnailer/more-debug

« back to all changes in this revision

Viewing changes to src/vs-thumb/thumbnailextractor.cpp

  • Committer: CI Train Bot
  • Author(s): Michi Henning
  • Date: 2016-01-07 04:36:36 UTC
  • mfrom: (129.1.22 landing-12.15)
  • Revision ID: ci-train-bot@canonical.com-20160107043636-xbao3rktj4uf3u0f
Merge changes from devel to trunk for landing.
Approved by: Michi Henning, PS Jenkins bot

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
#include "thumbnailextractor.h"
21
21
 
22
22
#include <QDebug>
 
23
#include <unity/util/ResourcePtr.h>
23
24
 
24
 
#include <cassert>
25
25
#include <cstdio>
26
26
#include <stdexcept>
27
27
#include <cstring>
28
28
 
 
29
#include <fcntl.h>
 
30
 
29
31
namespace unity
30
32
{
31
33
 
40
42
 
41
43
std::string const class_name = "ThumbnailExtractor";
42
44
 
43
 
class BufferMap final
44
 
{
45
 
public:
46
 
    BufferMap()
47
 
        : buffer(nullptr, gst_buffer_unref)
48
 
    {
49
 
    }
50
 
    ~BufferMap()
51
 
    {
52
 
        unmap();
53
 
    }
54
 
 
55
 
    void map(GstBuffer* b)
56
 
    {
57
 
        unmap();
58
 
        buffer.reset(gst_buffer_ref(b));
59
 
        gst_buffer_map(buffer.get(), &info, GST_MAP_READ);
60
 
    }
61
 
 
62
 
    void unmap()
63
 
    {
64
 
        if (!buffer)
65
 
        {
66
 
            return;
67
 
        }
68
 
        gst_buffer_unmap(buffer.get(), &info);
69
 
        buffer.reset();
70
 
    }
71
 
 
72
 
    guint8* data() const
73
 
    {
74
 
        assert(buffer);
75
 
        return info.data;
76
 
    }
77
 
 
78
 
    gsize size() const
79
 
    {
80
 
        assert(buffer);
81
 
        return info.size;
82
 
    }
83
 
 
84
 
private:
85
 
    std::unique_ptr<GstBuffer, decltype(&gst_buffer_unref)> buffer;
86
 
    GstMapInfo info;
87
 
};
88
 
 
89
45
// GstPlayFlags flags from playbin.
90
46
//
91
47
// GStreamer does not install headers for the enums of individual
128
84
        GstSample* s;
129
85
        if (!gst_tag_list_get_sample_index(tags, tag_name, i, &s))
130
86
        {
131
 
 
132
87
            break;
133
88
        }
134
89
        assert(s);
195
150
{
196
151
    change_state(playbin_.get(), GST_STATE_NULL);
197
152
    sample_.reset();
198
 
    sample_rotation_ = GDK_PIXBUF_ROTATE_NONE;
199
 
    sample_raw_ = true;
 
153
    still_frame_.reset();
200
154
}
201
155
 
202
156
void ThumbnailExtractor::set_uri(const std::string& uri)
205
159
    reset();
206
160
    uri_= uri;
207
161
    g_object_set(playbin_.get(), "uri", uri.c_str(), nullptr);
208
 
    qDebug().nospace() << uri_.c_str() << ": Changing to state PAUSED";
209
162
    change_state(playbin_.get(), GST_STATE_PAUSED);
210
163
 
211
164
    if (!gst_element_query_duration(playbin_.get(), GST_FORMAT_TIME, &duration_))
224
177
#pragma GCC diagnostic push
225
178
#pragma GCC diagnostic ignored "-Wold-style-cast"
226
179
 
 
180
namespace
 
181
{
 
182
 
 
183
extern "C"
 
184
void unmap_callback(guchar* /* pixels */, gpointer data)
 
185
{
 
186
    BufferMap* bm = reinterpret_cast<BufferMap*>(data);
 
187
    assert(bm);
 
188
    bm->unmap();
 
189
}
 
190
 
 
191
}
 
192
 
 
193
// Extract a still frame from a video. Rotate the frame as needed and leave it in still_frame_ in RGB format.
 
194
 
227
195
bool ThumbnailExtractor::extract_video_frame()
228
196
{
 
197
    // Seek some distance into the video so we don't always get black or a 20th Century Fox logo.
229
198
    gint64 seek_point = 10 * GST_SECOND;
230
199
    if (duration_ >= 0)
231
200
    {
232
201
        seek_point = 2 * duration_ / 7;
233
202
    }
234
 
    qDebug().nospace() << uri_.c_str() << ": Seeking to position " << int(seek_point / GST_SECOND);
235
203
    gst_element_seek_simple(playbin_.get(), GST_FORMAT_TIME,
236
204
                            static_cast<GstSeekFlags>(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), seek_point);
237
205
    gst_element_get_state(playbin_.get(), nullptr, nullptr, GST_CLOCK_TIME_NONE);
238
206
 
239
 
    // Retrieve sample from the playbin
240
 
    qDebug().nospace() << uri_.c_str() << ": Requesting sample frame";
 
207
    // Retrieve sample from the playbin.
241
208
    std::unique_ptr<GstCaps, decltype(&gst_caps_unref)> desired_caps(
242
209
        gst_caps_new_simple("video/x-raw", "format", G_TYPE_STRING, "RGB", "pixel-aspect-ratio", GST_TYPE_FRACTION, 1,
243
210
                            1, nullptr),
249
216
        throw_error("extract_video_frame(): failed to extract still frame");  // LCOV_EXCL_LINE
250
217
    }
251
218
    sample_.reset(s);
252
 
    sample_raw_ = true;
 
219
 
 
220
    // Convert raw sample into a pixbuf and store it in still_frame_.
 
221
    GstCaps* sample_caps = gst_sample_get_caps(sample_.get());
 
222
    if (!sample_caps)
 
223
    {
 
224
        throw_error("write_image(): Could not retrieve caps for sample buffer");  // LCOV_EXCL_LINE
 
225
    }
 
226
    GstStructure* sample_struct = gst_caps_get_structure(sample_caps, 0);
 
227
    int width = 0;
 
228
    int height = 0;
 
229
    gst_structure_get_int(sample_struct, "width", &width);
 
230
    gst_structure_get_int(sample_struct, "height", &height);
 
231
    if (width <= 0 || height <= 0)
 
232
    {
 
233
        throw_error("write_image(): Could not retrieve image dimensions");  // LCOV_EXCL_LINE
 
234
    }
 
235
 
 
236
    buffermap_.map(gst_sample_get_buffer(sample_.get()));
 
237
    still_frame_.reset(gdk_pixbuf_new_from_data(buffermap_.data(), GDK_COLORSPACE_RGB, FALSE, 8, width, height,
 
238
                                                GST_ROUND_UP_4(width * 3), unmap_callback, &buffermap_));
253
239
 
254
240
    // Does the sample need to be rotated?
255
 
    sample_rotation_ = GDK_PIXBUF_ROTATE_NONE;
 
241
    GdkPixbufRotation sample_rotation = GDK_PIXBUF_ROTATE_NONE;
256
242
    GstTagList* tags = nullptr;
257
243
    g_signal_emit_by_name(playbin_.get(), "get-video-tags", 0, &tags);
258
244
    if (tags)
263
249
        {
264
250
            if (!strcmp(orientation, "rotate-90"))
265
251
            {
266
 
                sample_rotation_ = GDK_PIXBUF_ROTATE_CLOCKWISE;
 
252
                sample_rotation = GDK_PIXBUF_ROTATE_CLOCKWISE;
267
253
            }
268
254
            else if (!strcmp(orientation, "rotate-180"))
269
255
            {
270
 
                sample_rotation_ = GDK_PIXBUF_ROTATE_UPSIDEDOWN;
 
256
                sample_rotation = GDK_PIXBUF_ROTATE_UPSIDEDOWN;
271
257
            }
272
258
            else if (!strcmp(orientation, "rotate-270"))
273
259
            {
274
 
                sample_rotation_ = GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE;
 
260
                sample_rotation = GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE;
275
261
            }
276
262
            else
277
263
            {
281
267
        }
282
268
        gst_tag_list_unref(tags);
283
269
    }
 
270
 
 
271
    if (sample_rotation != GDK_PIXBUF_ROTATE_NONE)
 
272
    {
 
273
        GdkPixbuf* rotated = gdk_pixbuf_rotate_simple(still_frame_.get(), sample_rotation);
 
274
        if (rotated)
 
275
        {
 
276
            still_frame_.reset(rotated);
 
277
        }
 
278
        else
 
279
        {
 
280
            // LCOV_EXCL_START
 
281
            qCritical() << "extract_video_frame(): gdk_pixbuf_rotate_simple() failed, "
 
282
                           "probably out of memory";
 
283
            // LCOV_EXCL_STOP
 
284
        }
 
285
    }
 
286
 
284
287
    return true;
285
288
}
286
289
 
287
290
#pragma GCC diagnostic pop
288
291
 
 
292
// Try to find an embedded image in an audio or video file.
 
293
// If an image cover was found, set sample_ to point at the image data and return true.
 
294
 
289
295
bool ThumbnailExtractor::extract_cover_art()
290
296
{
291
297
    GstTagList* tags = nullptr;
294
300
    {
295
301
        return false;  // LCOV_EXCL_LINE
296
302
    }
 
303
    std::unique_ptr<GstTagList, decltype(&gst_tag_list_unref)> tag_guard(tags, gst_tag_list_unref);
 
304
 
297
305
    sample_.reset();
298
 
    sample_rotation_ = GDK_PIXBUF_ROTATE_NONE;
299
 
    sample_raw_ = false;
300
306
 
301
307
    // Look for a normal image (cover or other image).
302
308
    auto image = std::move(find_cover(tags, GST_TAG_IMAGE));
310
316
    auto preview_image = find_cover(tags, GST_TAG_PREVIEW_IMAGE);
311
317
    if (preview_image.sample && preview_image.type == cover)
312
318
    {
 
319
        // Michi: I have no idea how to create an audio file with this tag :-(
 
320
        // LCOV_EXCL_START
313
321
        sample_ = std::move(preview_image.sample);
314
322
        return true;
 
323
        // LCOV_EXCL_STOP
315
324
    }
316
325
 
317
326
    // See if we found some other normal image.
324
333
    // We might have found a non-cover preview image.
325
334
    sample_ = std::move(preview_image.sample);
326
335
 
327
 
    gst_tag_list_unref(tags);
328
336
    return bool(sample_);
329
337
}
330
338
 
350
358
 
351
359
}
352
360
 
353
 
void ThumbnailExtractor::save_screenshot(const std::string& filename)
 
361
void ThumbnailExtractor::write_image(const std::string& filename)
354
362
{
355
 
    assert(sample_);
356
 
 
357
 
    // Construct a pixbuf from the sample
358
 
    qDebug().nospace() << uri_.c_str() << ": Saving image";
 
363
    assert(still_frame_ || sample_);
 
364
 
 
365
    // Figure out where to write to.
 
366
 
 
367
    int fd = STDOUT_FILENO;
 
368
    if (!filename.empty())
 
369
    {
 
370
        fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
 
371
        if (fd == -1)
 
372
        {
 
373
            auto msg = std::string("write_image(): cannot open ") + filename + ": " + strerror(errno);
 
374
            qCritical().nospace() << QString::fromStdString(msg);
 
375
            throw std::runtime_error(msg);
 
376
        }
 
377
    }
 
378
    auto close_func = [](int fd) { if (fd != STDOUT_FILENO) ::close(fd); };
 
379
    unity::util::ResourcePtr<int, decltype(close_func)> fd_guard(fd, close_func);  // Don't leak fd.
 
380
 
 
381
    if (still_frame_)
 
382
    {
 
383
        // We extracted a still frame from a video. We save as tiff without compression because that is
 
384
        // lossless and efficient. (There is no point avoiding the tiff encoding step because
 
385
        // still frame extraction is so slow that the gain would be insignificant.)
 
386
        GError* error = nullptr;
 
387
        if (!gdk_pixbuf_save_to_callback(still_frame_.get(), write_to_fd, &fd, "tiff", &error, "compression", "1", nullptr))
 
388
        {
 
389
            throw_error("write_image(): cannot write image", error);  // LCOV_EXCL_LINE
 
390
        }
 
391
        return;
 
392
    }
 
393
 
 
394
    // We found embedded artwork. The embedded data is already in some image format, such jpg or png.
 
395
    // If we are writing to stdout (to communicate with the thumbnailer), we just dump the image
 
396
    // as is; the thumbnailer will decode it.
359
397
    BufferMap buffermap;
360
 
    gobj_ptr<GdkPixbuf> image;
361
 
    if (sample_raw_)
362
 
    {
363
 
        GstCaps* sample_caps = gst_sample_get_caps(sample_.get());
364
 
        if (!sample_caps)
365
 
        {
366
 
            throw_error("save_screenshot(): Could not retrieve caps for sample buffer");  // LCOV_EXCL_LINE
367
 
        }
368
 
        GstStructure* sample_struct = gst_caps_get_structure(sample_caps, 0);
369
 
        int width = 0, height = 0;
370
 
        gst_structure_get_int(sample_struct, "width", &width);
371
 
        gst_structure_get_int(sample_struct, "height", &height);
372
 
        if (width <= 0 || height <= 0)
373
 
        {
374
 
            throw_error("save_screenshot(): Could not retrieve image dimensions");  // LCOV_EXCL_LINE
375
 
        }
376
 
 
377
 
        buffermap.map(gst_sample_get_buffer(sample_.get()));
378
 
        image.reset(gdk_pixbuf_new_from_data(buffermap.data(), GDK_COLORSPACE_RGB, FALSE, 8, width, height,
379
 
                                             GST_ROUND_UP_4(width * 3), nullptr, nullptr));
380
 
    }
381
 
    else
382
 
    {
383
 
        gobj_ptr<GdkPixbufLoader> loader(gdk_pixbuf_loader_new());
384
 
 
385
 
        buffermap.map(gst_sample_get_buffer(sample_.get()));
386
 
        GError* error = nullptr;
387
 
        if (gdk_pixbuf_loader_write(loader.get(), buffermap.data(), buffermap.size(), &error) &&
388
 
            gdk_pixbuf_loader_close(loader.get(), &error))
389
 
        {
390
 
            GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(loader.get());
391
 
            if (pixbuf)
392
 
            {
393
 
                image.reset(static_cast<GdkPixbuf*>(g_object_ref(pixbuf)));
394
 
            }
395
 
        }
396
 
        else
397
 
        {
398
 
            throw_error("save_screenshot(): decoding image", error);  // LCOV_EXCL_LINE
399
 
        }
400
 
    }
401
 
 
402
 
    if (sample_rotation_ != GDK_PIXBUF_ROTATE_NONE)
403
 
    {
404
 
        GdkPixbuf* rotated = gdk_pixbuf_rotate_simple(image.get(), sample_rotation_);
405
 
        if (rotated)
406
 
        {
407
 
            image.reset(rotated);
408
 
        }
409
 
    }
410
 
 
411
 
    // Saving as TIFF with no compression here to avoid artefacts due to converting to jpg twice.
412
 
    // (The main thumbnailer saves as jpg.) By staying lossless here, we
413
 
    // keep all the policy decisions about image quality in the main thumbnailer.
414
 
    // "compression", "1" means "no compression" for tiff files.
 
398
    buffermap.map(gst_sample_get_buffer(sample_.get()));
 
399
 
 
400
    if (fd == STDOUT_FILENO)
 
401
    {
 
402
        errno = 0;
 
403
        int rc = write(fd, buffermap.data(), buffermap.size());
 
404
        if (gsize(rc) != buffermap.size())
 
405
        {
 
406
            auto msg = std::string("write_image(): cannot write to ");
 
407
            msg += (filename.empty() ? std::string("stdout") : filename) + ": ";
 
408
            msg += errno != 0 ?  strerror(errno) : "short write";
 
409
            qCritical().nospace() << QString::fromStdString(msg);
 
410
            throw std::runtime_error(msg);
 
411
        }
 
412
        return;
 
413
    }
 
414
 
 
415
    // We were told to save to a file. Convert the sample data and write in tiff format.
 
416
    PixbufUPtr image_buf;
 
417
    gobj_ptr<GdkPixbufLoader> loader(gdk_pixbuf_loader_new());
415
418
    GError* error = nullptr;
416
 
    if (filename.empty())
 
419
    if (gdk_pixbuf_loader_write(loader.get(), buffermap.data(), buffermap.size(), &error) &&
 
420
        gdk_pixbuf_loader_close(loader.get(), &error))
417
421
    {
418
 
        // Write to stdout.
419
 
        int fd = 1;
420
 
        if (!gdk_pixbuf_save_to_callback(image.get(), write_to_fd, &fd, "tiff", &error, "compression", "1", nullptr))
 
422
        GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(loader.get());
 
423
        if (pixbuf)
421
424
        {
422
 
            throw_error("save_screenshot(): cannot write image to stdout", error);  // LCOV_EXCL_LINE
 
425
            image_buf.reset(static_cast<GdkPixbuf*>(g_object_ref(pixbuf)));
423
426
        }
424
427
    }
425
428
    else
426
429
    {
427
 
        if (!gdk_pixbuf_save(image.get(), filename.c_str(), "tiff", &error, "compression", "1", nullptr))
428
 
        {
429
 
            throw_error("save_screenshot(): cannot save image", error);  // LCOV_EXCL_LINE
430
 
        }
431
 
    }
432
 
    qDebug().nospace() << uri_.c_str() << ": Done";
 
430
        throw_error("write_image(): decoding image", error);  // LCOV_EXCL_LINE
 
431
    }
 
432
 
 
433
    if (!gdk_pixbuf_save_to_callback(image_buf.get(), write_to_fd, &fd, "tiff", &error, "compression", "1", nullptr))
 
434
    {
 
435
        throw_error("write_image(): cannot write image to stdout", error);  // LCOV_EXCL_LINE
 
436
    }
 
437
    return;
433
438
}
434
439
 
435
440
#pragma GCC diagnostic push