shotwell-video-thumbnailer.vala 3.99 KB
Newer Older
1
/* Copyright 2016 Software Freedom Conservancy Inc.
2 3 4 5 6
 * 
 * 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).
7
 * See the COPYING file in this distribution.
8 9 10 11 12
 */

// Shotwell Thumbnailer takes in a video file and returns a thumbnail to stdout.  This is
// a replacement for totem-video-thumbnailer
class ShotwellThumbnailer {
13
    const string caps_string = """video/x-raw,format=RGB,pixel-aspect-ratio=1/1""";
Jens Georg's avatar
Jens Georg committed
14

15 16 17 18
    public static int main(string[] args) {
        Gst.Element pipeline, sink;
        string descr;
        Gdk.Pixbuf pixbuf;
19
        uint8[]? pngdata;
20
        int64 duration, position;
21
        Gst.StateChangeReturn ret;
22 23 24 25 26

        if (Posix.nice (19) < 0) {
            debug ("Failed to reduce thumbnailer nice level. Continuing anyway");
        }

27
        Gst.init(ref args);
Jens Georg's avatar
Jens Georg committed
28 29

        var registry = Gst.Registry.@get ();
Jens Georg's avatar
Jens Georg committed
30 31 32
        var features = registry.feature_filter ((f) => {
            return f.get_name ().has_prefix ("vaapi");
        }, false);
Jens Georg's avatar
Jens Georg committed
33

Jens Georg's avatar
Jens Georg committed
34 35
        foreach (var feature in features) {
            debug ("Removing registry feature %s", feature.get_name ());
Jens Georg's avatar
Jens Georg committed
36 37 38
            registry.remove_feature (feature);
        }

39 40 41 42 43
        if (args.length != 2) {
            stdout.printf("usage: %s [filename]\n Writes video thumbnail to stdout\n", args[0]);
            return 1;
        }
        
44
        descr = "filesrc location=\"%s\" ! decodebin ! videoconvert ! videoscale ! ".printf(args[1]) +
45
            "%s ! gdkpixbufsink name=sink".printf(caps_string);
46 47 48 49 50 51 52 53 54 55 56
        
        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) {
57
                warning("Failed to play the file: couldn't set state\n");
58 59
                return 3;
            } else if (ret == Gst.StateChangeReturn.NO_PREROLL) {
60
                warning("Live sources not supported yet.\n");
61 62 63 64 65 66 67 68
                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) {
69
                warning("Failed to play the file: couldn't get state.\n");
70 71
                return 3;
            }
72 73

            /* get the duration */
74
            if (!pipeline.query_duration (Gst.Format.TIME, out duration)) {
75
                warning("Failed to query file for duration\n");
76 77
                return 3;
            }
78

79
            position = 1 * Gst.SECOND;
80 81 82 83 84 85 86

            /* 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);

87 88
            ret = pipeline.get_state(null, null, 5 * Gst.SECOND);
            if (ret == Gst.StateChangeReturn.FAILURE) {
89
                warning("Failed to play the file: couldn't get state.\n");
90 91
                return 3;
            }
92

93 94 95 96 97
            sink.get ("last-pixbuf", out pixbuf);

            // Save the pixbuf.
            pixbuf.save_to_buffer(out pngdata, "png");
            stdout.write(pngdata);
98 99 100 101 102

            // cleanup and exit.
            pipeline.set_state(Gst.State.NULL);
            
        } catch (Error e) {
103
            warning(e.message);
104 105 106 107 108 109 110
            return 2;
        }
        
        return 0;
    }
}