224
177
#pragma GCC diagnostic push
225
178
#pragma GCC diagnostic ignored "-Wold-style-cast"
184
void unmap_callback(guchar* /* pixels */, gpointer data)
186
BufferMap* bm = reinterpret_cast<BufferMap*>(data);
193
// Extract a still frame from a video. Rotate the frame as needed and leave it in still_frame_ in RGB format.
227
195
bool ThumbnailExtractor::extract_video_frame()
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)
232
201
seek_point = 2 * duration_ / 7;
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);
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,
249
216
throw_error("extract_video_frame(): failed to extract still frame"); // LCOV_EXCL_LINE
251
218
sample_.reset(s);
220
// Convert raw sample into a pixbuf and store it in still_frame_.
221
GstCaps* sample_caps = gst_sample_get_caps(sample_.get());
224
throw_error("write_image(): Could not retrieve caps for sample buffer"); // LCOV_EXCL_LINE
226
GstStructure* sample_struct = gst_caps_get_structure(sample_caps, 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)
233
throw_error("write_image(): Could not retrieve image dimensions"); // LCOV_EXCL_LINE
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_));
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);
353
void ThumbnailExtractor::save_screenshot(const std::string& filename)
361
void ThumbnailExtractor::write_image(const std::string& filename)
357
// Construct a pixbuf from the sample
358
qDebug().nospace() << uri_.c_str() << ": Saving image";
363
assert(still_frame_ || sample_);
365
// Figure out where to write to.
367
int fd = STDOUT_FILENO;
368
if (!filename.empty())
370
fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
373
auto msg = std::string("write_image(): cannot open ") + filename + ": " + strerror(errno);
374
qCritical().nospace() << QString::fromStdString(msg);
375
throw std::runtime_error(msg);
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.
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))
389
throw_error("write_image(): cannot write image", error); // LCOV_EXCL_LINE
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;
363
GstCaps* sample_caps = gst_sample_get_caps(sample_.get());
366
throw_error("save_screenshot(): Could not retrieve caps for sample buffer"); // LCOV_EXCL_LINE
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)
374
throw_error("save_screenshot(): Could not retrieve image dimensions"); // LCOV_EXCL_LINE
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));
383
gobj_ptr<GdkPixbufLoader> loader(gdk_pixbuf_loader_new());
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))
390
GdkPixbuf* pixbuf = gdk_pixbuf_loader_get_pixbuf(loader.get());
393
image.reset(static_cast<GdkPixbuf*>(g_object_ref(pixbuf)));
398
throw_error("save_screenshot(): decoding image", error); // LCOV_EXCL_LINE
402
if (sample_rotation_ != GDK_PIXBUF_ROTATE_NONE)
404
GdkPixbuf* rotated = gdk_pixbuf_rotate_simple(image.get(), sample_rotation_);
407
image.reset(rotated);
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()));
400
if (fd == STDOUT_FILENO)
403
int rc = write(fd, buffermap.data(), buffermap.size());
404
if (gsize(rc) != buffermap.size())
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);
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))
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());
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)));
427
if (!gdk_pixbuf_save(image.get(), filename.c_str(), "tiff", &error, "compression", "1", nullptr))
429
throw_error("save_screenshot(): cannot save image", error); // LCOV_EXCL_LINE
432
qDebug().nospace() << uri_.c_str() << ": Done";
430
throw_error("write_image(): decoding image", error); // LCOV_EXCL_LINE
433
if (!gdk_pixbuf_save_to_callback(image_buf.get(), write_to_fd, &fd, "tiff", &error, "compression", "1", nullptr))
435
throw_error("write_image(): cannot write image to stdout", error); // LCOV_EXCL_LINE
435
440
#pragma GCC diagnostic push