/* Copyright 2011-2014 Yorba Foundation * * This is a Vala-rewrite of GStreamer snapshot example. Adapted from earlier * work from Wim Taymans. * * This software is licensed under the GNU LGPL (version 2.1 or later). * See the COPYING file in this distribution. */ // Shotwell Thumbnailer takes in a video file and returns a thumbnail to stdout. This is // a replacement for totem-video-thumbnailer class ShotwellThumbnailer { const string caps_string = """video/x-raw,format=RGB,pixel-aspect-ratio=1/1"""; public static int main(string[] args) { Gst.Element pipeline, sink; int width, height; Gst.Sample sample; string descr; Gdk.Pixbuf pixbuf; int64 duration, position; Gst.StateChangeReturn ret; bool res; Gst.init(ref args); if (args.length != 2) { stdout.printf("usage: %s [filename]\n Writes video thumbnail to stdout\n", args[0]); return 1; } descr = "filesrc location=\"%s\" ! decodebin ! videoconvert ! videoscale ! ".printf(args[1]) + "appsink name=sink caps=\"%s\"".printf(caps_string); try { // Create new pipeline. pipeline = Gst.parse_launch(descr); // Get sink. sink = ((Gst.Bin) pipeline).get_by_name("sink"); // Set to PAUSED to make the first frame arrive in the sink. ret = pipeline.set_state(Gst.State.PAUSED); if (ret == Gst.StateChangeReturn.FAILURE) { stderr.printf("Failed to play the file: couldn't set state\n"); return 3; } else if (ret == Gst.StateChangeReturn.NO_PREROLL) { stderr.printf("Live sources not supported yet.\n"); return 4; } // This can block for up to 5 seconds. If your machine is really overloaded, // it might time out before the pipeline prerolled and we generate an error. A // better way is to run a mainloop and catch errors there. ret = pipeline.get_state(null, null, 5 * Gst.SECOND); if (ret == Gst.StateChangeReturn.FAILURE) { stderr.printf("Failed to play the file: couldn't get state.\n"); return 3; } /* get the duration */ pipeline.query_duration (Gst.Format.TIME, out duration); position = 1 * Gst.SECOND; /* seek to the a position in the file. Most files have a black first frame so * by seeking to somewhere else we have a bigger chance of getting something * more interesting. An optimisation would be to detect black images and then * seek a little more */ pipeline.seek_simple (Gst.Format.TIME, Gst.SeekFlags.KEY_UNIT | Gst.SeekFlags.FLUSH, position); /* get the preroll buffer from appsink, this block untils appsink really * prerolls */ GLib.Signal.emit_by_name (sink, "pull-preroll", out sample, null); // if we have a buffer now, convert it to a pixbuf. It's possible that we // don't have a buffer because we went EOS right away or had an error. if (sample != null) { Gst.Buffer buffer; Gst.Caps caps; unowned Gst.Structure s; Gst.MapInfo mapinfo; uint8[]? pngdata; // Get the snapshot buffer format now. We set the caps on the appsink so // that it can only be an rgb buffer. The only thing we have not specified // on the caps is the height, which is dependent on the pixel-aspect-ratio // of the source material. caps = sample.get_caps(); if (caps == null) { stderr.printf("could not get snapshot format\n"); return 5; } s = caps.get_structure(0); // We need to get the final caps on the buffer to get the size. res = s.get_int("width", out width); res |= s.get_int("height", out height); if (!res) { stderr.printf("Could not get snapshot dimension\n"); return 6; } buffer = sample.get_buffer(); buffer.map(out mapinfo, Gst.MapFlags.READ); if (mapinfo.data == null || mapinfo.data.length == 0) { stderr.printf("Could not get snapshot data buffer\n"); return 7; } // Create pixmap from buffer and save, gstreamer video buffers have a stride // that is rounded up to the nearest multiple of 4. pixbuf = new Gdk.Pixbuf.from_data(mapinfo.data, Gdk.Colorspace.RGB, false, 8, width, height, (((width * 3)+3)&~3), null); // Save the pixbuf. pixbuf.save_to_buffer(out pngdata, "png"); stdout.write(pngdata); buffer.unmap(mapinfo); } else { stderr.printf("Could not make snapshot\n"); return 10; } // cleanup and exit. pipeline.set_state(Gst.State.NULL); } catch (Error e) { stderr.printf(e.message); return 2; } return 0; } }