[rygel] media-export: Extract meta-data in external process



commit 1e1dc0992211cfccfa3a2214afb1a500589bcc07
Author: Jens Georg <mail jensge org>
Date:   Wed Jun 3 20:20:15 2015 +0200

    media-export: Extract meta-data in external process
    
    Move GStreamer calls to extract meta-data into own process.
    This enables us to recover from crashes due to bugs with broken media files,
    knowing exactly which URI caused the crash and (later) blacklist the broken
    files.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=749790

 configure.ac                                       |    1 +
 src/plugins/media-export/Makefile.am               |   31 ++
 .../media-export/rygel-media-export-extract.vala   |  238 ++++++++++++
 .../media-export/rygel-media-export-harvester.vala |    1 +
 .../rygel-media-export-harvesting-task.vala        |   39 +-
 .../rygel-media-export-info-serializer.vala        |  215 +++++++++++
 .../rygel-media-export-item-factory.vala           |  387 ++++++++++----------
 .../rygel-media-export-metadata-extractor.vala     |  290 +++++++++++----
 8 files changed, 903 insertions(+), 299 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index a7955aa..b781502 100644
--- a/configure.ac
+++ b/configure.ac
@@ -182,6 +182,7 @@ AS_IF([test "x$with_media_engine" = "xgstreamer"],
           [
             PKG_CHECK_MODULES([RYGEL_PLUGIN_MEDIA_EXPORT_DEPS],
                               [$RYGEL_COMMON_MODULES gio-2.0 >= $GIO_REQUIRED
+                               gio-unix-2.0 >= $GIO_REQUIRED
                                gupnp-dlna-2.0 >= $GUPNP_DLNA_REQUIRED
                                gupnp-dlna-gst-2.0 >= $GUPNP_DLNA_REQUIRED
                                gstreamer-app-1.0 >= $GSTREAMER_APP_REQUIRED
diff --git a/src/plugins/media-export/Makefile.am b/src/plugins/media-export/Makefile.am
index 4916bcc..ee19f16 100644
--- a/src/plugins/media-export/Makefile.am
+++ b/src/plugins/media-export/Makefile.am
@@ -1,5 +1,33 @@
+if UNINSTALLED
+MX_EXTRACT_PATH=$(abs_builddir)/mx-extract
+else
+MX_EXTRACT_PATH=$(libexecdir)/mx-extract
+endif
+
 include $(top_srcdir)/common.am
 
+## Extraction helper
+pkglibexec_PROGRAMS = mx-extract
+mx_extract_SOURCES = \
+       rygel-media-export-extract.vala \
+       rygel-media-export-info-serializer.vala
+
+mx_extract_VALAFLAGS = \
+       --enable-experimental \
+       --pkg posix \
+       --pkg gio-unix-2.0 \
+       $(RYGEL_PLUGIN_MEDIA_EXPORT_DEPS_VALAFLAGS) \
+       $(RYGEL_COMMON_VALAFLAGS)
+
+mx_extract_CFLAGS = \
+       $(RYGEL_PLUGIN_MEDIA_EXPORT_DEPS_CFLAGS) \
+       $(RYGEL_COMMON_LIBRYGEL_SERVER_CFLAGS) \
+       -DG_LOG_DOMAIN='"MediaExport"'
+
+mx_extract_LDADD = \
+       $(RYGEL_PLUGIN_MEDIA_EXPORT_DEPS_LIBS)
+
+## Plugin
 plugin_LTLIBRARIES = librygel-media-export.la
 plugin_DATA = media-export.plugin
 
@@ -43,6 +71,8 @@ librygel_media_export_la_VALAFLAGS = \
        --internal-vapi rygel-media-export.vapi \
        --internal-header rygel-media-export-internal.h \
        --header rygel-media-export.h \
+       --pkg posix \
+       --pkg gio-unix-2.0 \
        $(RYGEL_PLUGIN_MEDIA_EXPORT_DEPS_VALAFLAGS) \
        $(RYGEL_COMMON_LIBRYGEL_SERVER_VALAFLAGS) \
        $(RYGEL_COMMON_VALAFLAGS)
@@ -50,6 +80,7 @@ librygel_media_export_la_VALAFLAGS = \
 librygel_media_export_la_CFLAGS = \
        $(RYGEL_PLUGIN_MEDIA_EXPORT_DEPS_CFLAGS) \
        $(RYGEL_COMMON_LIBRYGEL_SERVER_CFLAGS) \
+       -DMX_EXTRACT_PATH='"$(MX_EXTRACT_PATH)"' \
        -DG_LOG_DOMAIN='"MediaExport"'
 librygel_media_export_la_LIBADD = \
        $(RYGEL_PLUGIN_MEDIA_EXPORT_DEPS_LIBS) \
diff --git a/src/plugins/media-export/rygel-media-export-extract.vala 
b/src/plugins/media-export/rygel-media-export-extract.vala
new file mode 100644
index 0000000..a1fb0c5
--- /dev/null
+++ b/src/plugins/media-export/rygel-media-export-extract.vala
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2015 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Gst.PbUtils;
+using GUPnPDLNA;
+using Gst;
+
+const string UPNP_CLASS_PHOTO = "object.item.imageItem.photo";
+const string UPNP_CLASS_MUSIC = "object.item.audioItem.musicTrack";
+const string UPNP_CLASS_VIDEO = "object.item.videoItem";
+const string UPNP_CLASS_PLAYLIST = "object.item.playlistItem";
+
+const string STATUS_LINE_TEMPLATE = "RESULT|%s|%" + size_t.FORMAT + "|%s\n";
+const string ERROR_LINE_TEMPLATE = "ERROR|%s|%d|%s\n";
+
+const string FATAL_ERROR_PREFIX = "FATAL_ERROR|";
+const string FATAL_ERROR_SUFFIX = "\n"; //|0|Killed by signal\n";
+
+static int in_fd = 0;
+static int out_fd = 1;
+static int err_fd = 2;
+
+MainLoop loop;
+
+DataInputStream input_stream;
+OutputStream output_stream;
+OutputStream error_stream;
+Rygel.InfoSerializer serializer;
+
+public errordomain MetadataExtractorError {
+    GENERAL
+}
+
+static const OptionEntry[] options = {
+    { "input-fd", 'i', 0, OptionArg.INT, ref in_fd, "File descriptor used for input", null },
+    { "output-fd", 'o', 0, OptionArg.INT, ref out_fd, "File descriptor used for output", null },
+    { "error-fd", 'e', 0, OptionArg.INT, ref err_fd, "File descriptor used for severe errors", null },
+    { null }
+};
+
+Discoverer discoverer;
+ProfileGuesser guesser;
+
+static uint8 last_uri_data[4096];
+size_t last_uri_data_length;
+
+static void segv_handler (int signal) {
+    Posix.write (err_fd, (void *) last_uri_data, last_uri_data_length);
+    Posix.write (err_fd, (void *) FATAL_ERROR_SUFFIX, 1);
+    Posix.fsync (err_fd);
+
+    Posix.exit(-1);
+}
+
+async void run () {
+    while (true) {
+        try {
+            var line = yield input_stream.read_line_async ();
+            if (line == null) {
+                break;
+            }
+
+            if (line.has_prefix ("EXTRACT ")) {
+                debug ("Got command to extract file: %s", line);
+                var uri = line.replace ("EXTRACT ", "").strip ();
+                DiscovererInfo? info = null;
+                try {
+                    // Copy current URI to statically allocated memory area to
+                    // dump to fd in the signal handler
+                    last_uri_data_length = uri.length;
+                    GLib.Memory.set (last_uri_data, 0, 4096);
+                    GLib.Memory.copy (last_uri_data, (void *) uri, uri.length);
+                    info = discoverer.discover_uri (uri);
+
+                    debug ("Finished discover on uri %s", uri);
+                    yield on_discovered (info);
+                } catch (Error error) {
+                    warning (_("Failed to discover uri %s: %s"),
+                             uri,
+                             error.message);
+                    send_error (File.new_for_uri (uri), error);
+
+                    // Recreate the discoverer on error
+                    discoverer = new Discoverer (10 * Gst.SECOND);
+                }
+                //discoverer.discover_uri_async (uri);
+            } else if (line.has_prefix ("QUIT")) {
+                break;
+            }
+        } catch (Error error) {
+            warning (_("Failed to read from pipe: %s"), error.message);
+
+            break;
+        }
+    }
+
+    loop.quit ();
+}
+
+static void send_extraction_done (File file, Variant v) throws Error {
+    var data = v.get_data_as_bytes ();
+    size_t bytes_written = 0;
+    var status = STATUS_LINE_TEMPLATE.printf (file.get_uri (),
+                                              data.get_size (),
+                                              file.get_uri ());
+
+    output_stream.write_all (status.data, out bytes_written);
+    output_stream.write_all (data.get_data (), out bytes_written);
+}
+
+static void send_error (File file, Error err) {
+    size_t bytes_written = 0;
+    var status = ERROR_LINE_TEMPLATE.printf (file.get_uri (),
+                                             err.code,
+                                             err.message);
+    try {
+        output_stream.write_all (status.data, out bytes_written);
+    } catch (Error error) {
+        warning (_("Failed to send error to parent: %s"), error.message);
+    }
+}
+
+static async void on_discovered (DiscovererInfo info) {
+    debug ("Discovered %s", info.get_uri ());
+    var file = File.new_for_uri (info.get_uri ());
+    if (info.get_result () == DiscovererResult.TIMEOUT ||
+        info.get_result () == DiscovererResult.BUSY ||
+        info.get_result () == DiscovererResult.MISSING_PLUGINS) {
+        if (info.get_result () == DiscovererResult.MISSING_PLUGINS) {
+            debug ("Plugins are missing for extraction of file %s",
+                   info.get_uri ());
+        } else {
+            debug ("Extraction timed out on %s", file.get_uri ());
+        }
+        yield extract_basic_information (file, null, null);
+
+        return;
+    }
+
+    var dlna_info = GUPnPDLNAGst.utils_information_from_discoverer_info (info);
+    var dlna = guesser.guess_profile_from_info (dlna_info);
+    yield extract_basic_information (file, info, dlna);
+}
+
+static async void extract_basic_information (File               file,
+                                             DiscovererInfo?    info,
+                                             GUPnPDLNA.Profile? dlna) {
+    FileInfo file_info;
+
+    try {
+        file_info = yield file.query_info_async (FileAttribute.STANDARD_CONTENT_TYPE
+                                                 + "," +
+                                                 FileAttribute.STANDARD_SIZE + "," +
+                                                 FileAttribute.TIME_MODIFIED + "," +
+                                                 FileAttribute.STANDARD_DISPLAY_NAME,
+                                                 FileQueryInfoFlags.NONE);
+    } catch (Error error) {
+        var uri = file.get_uri ();
+
+        warning (_("Failed to extract basic metadata from %s: %s"),
+                 uri,
+                 error.message);
+
+        // signal error to parent
+        send_error (file, error);
+
+        return;
+    }
+
+    try {
+        send_extraction_done (file,
+                              serializer.serialize (file, file_info, info, dlna));
+    } catch (Error error) {
+        send_error (file, error);
+    }
+}
+
+int main (string[] args) {
+    var ctx = new OptionContext (_("- helper binary for Rygel to extract meta-data"));
+    ctx.add_main_entries (options, null);
+    ctx.add_group (Gst.init_get_option_group ());
+
+    try {
+        ctx.parse (ref args);
+    } catch (Error error) {
+        warning (_("Failed to parse commandline args: %s"), error.message);
+
+        return Posix.EXIT_FAILURE;
+    }
+
+    serializer = new Rygel.InfoSerializer ();
+    Posix.nice (19);
+
+    var action = new Posix.sigaction_t ();
+    action.sa_handler = segv_handler;
+    Posix.sigaction (Posix.SIGSEGV, action, null);
+    Posix.sigaction (Posix.SIGABRT, action, null);
+
+    message ("Started with descriptors %d %d %d", in_fd, out_fd, err_fd);
+
+    input_stream = new DataInputStream (new UnixInputStream (in_fd, false));
+    output_stream = new UnixOutputStream (out_fd, false);
+    error_stream = new UnixOutputStream (err_fd, false);
+
+    loop = new MainLoop ();
+    try {
+        discoverer = new Discoverer (10 * Gst.SECOND);
+    } catch (Error error) {
+        warning (_("Failed to start meta-data discoverer: %s"),
+                 error.message);
+    }
+
+    guesser = new ProfileGuesser (true, true);
+
+    run.begin ();
+    loop.run ();
+
+    return 0;
+}
diff --git a/src/plugins/media-export/rygel-media-export-harvester.vala 
b/src/plugins/media-export/rygel-media-export-harvester.vala
index 1c9038b..d1e077c 100644
--- a/src/plugins/media-export/rygel-media-export-harvester.vala
+++ b/src/plugins/media-export/rygel-media-export-harvester.vala
@@ -74,6 +74,7 @@ internal class Rygel.MediaExport.Harvester : GLib.Object {
                info.get_content_type () == "application/xml" ||
                info.get_content_type () == "text/xml" ||
                info.get_content_type () == "text/plain";
+        // Todo: Check blacklist
     }
 
     /**
diff --git a/src/plugins/media-export/rygel-media-export-harvesting-task.vala 
b/src/plugins/media-export/rygel-media-export-harvesting-task.vala
index fd019f4..69bd103 100644
--- a/src/plugins/media-export/rygel-media-export-harvesting-task.vala
+++ b/src/plugins/media-export/rygel-media-export-harvesting-task.vala
@@ -63,19 +63,24 @@ public class Rygel.MediaExport.HarvestingTask : Rygel.StateMachine,
         this.parent = parent;
         this.cache = MediaCache.get_default ();
 
-        this.extractor.extraction_done.connect (on_extracted_cb);
-        this.extractor.error.connect (on_extractor_error_cb);
+        this.extractor.extraction_done.connect (this.on_extracted_cb);
+        this.extractor.error.connect (this.on_extractor_error_cb);
 
         this.files = new LinkedList<FileQueueEntry> ();
         this.containers = new GLib.Queue<MediaContainer> ();
         this.monitor = monitor;
     }
 
+    ~HarvestingTask () {
+        this.extractor.stop ();
+    }
+
     public void cancel () {
         // detach from common cancellable; otherwise everything would be
         // cancelled like file monitoring, other harvesters etc.
         this.cancellable = new Cancellable ();
         this.cancellable.cancel ();
+        this.extractor.stop ();
     }
 
     /**
@@ -95,6 +100,8 @@ public class Rygel.MediaExport.HarvestingTask : Rygel.StateMachine,
      */
     public async void run () {
         try {
+            this.extractor.run.begin ();
+
             var info = yield this.origin.query_info_async
                                         (HARVESTER_ATTRIBUTES,
                                          FileQueryInfoFlags.NONE,
@@ -110,6 +117,7 @@ public class Rygel.MediaExport.HarvestingTask : Rygel.StateMachine,
                 this.completed ();
             }
         } catch (Error error) {
+            this.extractor.stop ();
             if (!(error is IOError.CANCELLED)) {
                 warning (_("Failed to harvest file %s: %s"),
                          this.origin.get_uri (),
@@ -283,25 +291,18 @@ public class Rygel.MediaExport.HarvestingTask : Rygel.StateMachine,
     }
 
     private void on_extracted_cb (File               file,
-                                  DiscovererInfo?    discoverer_info,
-                                  GUPnPDLNA.Profile? dlna_profile,
-                                  FileInfo           file_info) {
+                                  Variant            info) {
+        if (!file.equal (this.files.peek ().file)) {
+            debug ("Not for us, ignoring");
+        }
+
         if (this.cancellable.is_cancelled ()) {
             this.completed ();
         }
 
-        MediaFileItem item;
-        if (discoverer_info == null) {
-            item = ItemFactory.create_simple (this.containers.peek_head (),
-                                              file,
-                                              file_info);
-        } else {
-            item = ItemFactory.create_from_info (this.containers.peek_head (),
-                                                 file,
-                                                 discoverer_info,
-                                                 dlna_profile,
-                                                 file_info);
-        }
+        var item = ItemFactory.create_from_variant (this.containers.peek_head (),
+                                                    file,
+                                                    info);
 
         if (item != null) {
             item.parent_ref = this.containers.peek_head ();
@@ -324,10 +325,12 @@ public class Rygel.MediaExport.HarvestingTask : Rygel.StateMachine,
         // failed; there's not much to do here, just print the information and
         // go to the next file
 
-        debug ("Skipping %s; extraction completely failed: %s",
+        warning ("Skipping %s; extraction completely failed: %s",
                file.get_uri (),
                error.message);
 
+        // TODO: Add to blacklist
+
         this.files.poll ();
         this.do_update ();
     }
diff --git a/src/plugins/media-export/rygel-media-export-info-serializer.vala 
b/src/plugins/media-export/rygel-media-export-info-serializer.vala
new file mode 100644
index 0000000..3e46caa
--- /dev/null
+++ b/src/plugins/media-export/rygel-media-export-info-serializer.vala
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2015 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Gst;
+using Gst.PbUtils;
+
+internal errordomain InfoSerializerError {
+    INVALID_STREAM,
+    BAD_MIME
+}
+
+internal class Rygel.InfoSerializer {
+    public Variant serialize (File               file,
+                              FileInfo           file_info,
+                              DiscovererInfo?    info,
+                              GUPnPDLNA.Profile? dlna_profile) throws Error {
+        // Guess UPnP class
+        if (info != null) {
+            string? upnp_class = null;
+
+            var audio_streams = (GLib.List<DiscovererAudioInfo>)
+                                            info.get_audio_streams ();
+            var video_streams = (GLib.List<DiscovererVideoInfo>)
+                                            info.get_video_streams ();
+            if (audio_streams == null && video_streams == null) {
+                debug ("%s had neither audio nor video/picture " +
+                       "streams. Ignoring.",
+                       file.get_uri ());
+
+                throw new InfoSerializerError.INVALID_STREAM ("No stream information");
+            }
+
+            if (audio_streams == null && video_streams.data.is_image ()) {
+                upnp_class = UPNP_CLASS_PHOTO;
+            } else if (video_streams != null) {
+                upnp_class = UPNP_CLASS_VIDEO;
+            } else if (audio_streams != null) {
+                upnp_class = UPNP_CLASS_MUSIC;
+            } else {
+                // Uh...
+            }
+
+            return new Variant ("(smvmvmvmvmvmv)",
+                                upnp_class,
+                                this.serialize_file_info (file_info),
+                                this.serialize_dlna_profile (dlna_profile),
+                                this.serialize_info (info),
+                                this.serialize_audio_info (audio_streams != null ?
+                                                           audio_streams.data : null),
+                                this.serialize_video_info (video_streams != null ?
+                                                           video_streams.data : null),
+                                this.serialize_meta_data (audio_streams != null ?
+                                                          audio_streams.data : null));
+        } else {
+            string? upnp_class = null;
+            var mime = ContentType.get_mime_type (file_info.get_content_type ());
+            if (mime.has_prefix ("video/")) {
+                upnp_class = UPNP_CLASS_VIDEO;
+            } else if (mime.has_prefix ("image/")) {
+                upnp_class = UPNP_CLASS_PHOTO;
+            } else if (mime.has_prefix ("audio/") || mime == "application/ogg") {
+                upnp_class = UPNP_CLASS_MUSIC;
+            } else if (mime.has_suffix ("/xml")) { // application/xml or text/xml
+                upnp_class = UPNP_CLASS_PLAYLIST;
+            } else {
+                debug ("Unsupported content-type %s, skipping %s…",
+                       mime,
+                       file.get_uri ());
+
+                throw new InfoSerializerError.BAD_MIME ("Not supported: %s", mime);
+            }
+
+            return new Variant ("(ssmvmvmvmvmvmv)",
+                                file.get_uri (),
+                                upnp_class,
+                                this.serialize_file_info (file_info),
+                                null,
+                                null,
+                                null,
+                                null,
+                                null);
+        }
+    }
+
+    private Variant serialize_file_info (FileInfo info) {
+        return new Variant ("(sstt)",
+                            info.get_display_name (),
+                            ContentType.get_mime_type
+                                        (info.get_content_type ()),
+                            info.get_attribute_uint64
+                                        (FileAttribute.TIME_MODIFIED),
+                            info.get_size ());
+    }
+
+    private Variant? serialize_dlna_profile (GUPnPDLNA.Profile? profile) {
+        if (profile == null) {
+            return null;
+        }
+
+        return new Variant ("(ss)", profile.name, profile.mime);
+    }
+
+    private Variant? serialize_info (DiscovererInfo? info) {
+        long duration = -1;
+        if (info.get_duration () > 0) {
+            duration = (long) (info.get_duration () / Gst.SECOND);
+        }
+
+        var tags = info.get_tags ();
+        string? title = null;
+        if (tags != null) {
+            tags.get_string (Tags.TITLE, out title);
+        }
+
+        string date = null;
+        Gst.DateTime? dt = null;
+        if (tags != null && tags.get_date_time (Tags.DATE_TIME, out dt)) {
+            // Make a minimal valid iso8601 date - bgo#702231
+            // This mostly happens with MP3 files which only have a year
+            if (!dt.has_day () || !dt.has_month ()) {
+                date = "%d-%02d-%02d".printf (dt.get_year (),
+                                              dt.has_month () ?
+                                                  dt.get_month () : 1,
+                                              dt.has_day () ?
+                                                  dt.get_day () : 1);
+            } else {
+                date = dt.to_iso8601_string ();
+            }
+        }
+
+        return new Variant ("(msmsi)",
+                            title,
+                            date,
+                            duration);
+    }
+
+    private Variant? serialize_video_info (DiscovererVideoInfo? info) {
+        if (info == null) {
+            return null;
+        }
+
+        return new Variant ("(iii)",
+                            (int) info.get_width (),
+                            (int) info.get_height (),
+                            info.get_depth () > 0 ?
+                                info.get_depth () : -1);
+    }
+
+    private Variant? serialize_audio_info (DiscovererAudioInfo? info) {
+        if (info == null) {
+            return null;
+        }
+
+        return new Variant ("(ii)",
+                            (int) info.get_channels (),
+                            (int) info.get_sample_rate ());
+
+    }
+
+    private Variant? serialize_meta_data (DiscovererAudioInfo? info) {
+        if (info == null) {
+            return null;
+        }
+
+        var tags = info.get_tags ();
+        if (tags == null) {
+            return null;
+        }
+
+        string artist = null;
+        tags.get_string (Tags.ARTIST, out artist);
+
+        string album = null;
+        tags.get_string (Tags.ALBUM, out album);
+
+        string genre = null;
+        tags.get_string (Tags.GENRE, out genre);
+
+        uint volume = uint.MAX;
+        tags.get_uint (Tags.ALBUM_VOLUME_NUMBER, out volume);
+
+        uint track = uint.MAX;
+        tags.get_uint (Tags.TRACK_NUMBER, out track);
+
+        uint bitrate = uint.MAX;
+        tags.get_uint (Tags.BITRATE, out bitrate);
+
+        return new Variant ("(msmsmsiii)",
+                            artist,
+                            album,
+                            genre,
+                            volume,
+                            track,
+                            ((int) bitrate) / 8);
+    }
+}
diff --git a/src/plugins/media-export/rygel-media-export-item-factory.vala 
b/src/plugins/media-export/rygel-media-export-item-factory.vala
index a0866b2..b6f73ed 100644
--- a/src/plugins/media-export/rygel-media-export-item-factory.vala
+++ b/src/plugins/media-export/rygel-media-export-item-factory.vala
@@ -113,261 +113,248 @@ namespace Rygel.MediaExport.ItemFactory {
         }
     }
 
-    public static MediaFileItem? create_from_info (MediaContainer     parent,
-                                                   File               file,
-                                                   DiscovererInfo     info,
-                                                   GUPnPDLNA.Profile? profile,
-                                                   FileInfo           file_info) {
-        MediaFileItem item;
-        string id = MediaCache.get_id (file);
-        GLib.List<DiscovererAudioInfo> audio_streams;
-        GLib.List<DiscovererVideoInfo> video_streams;
+    static MediaFileItem? create_from_variant (MediaContainer parent,
+                                               File           file,
+                                               Variant        v) {
+        if (!v.is_of_type (new VariantType ("(smvmvmvmvmvmv)"))) {
+            warning ("Invalid meta-data serialisation, cannot process %s",
+                     v.get_type_string ());
+
+            return null;
+        }
 
-        audio_streams = (GLib.List<DiscovererAudioInfo>)
-                                        info.get_audio_streams ();
-        video_streams = (GLib.List<DiscovererVideoInfo>)
-                                        info.get_video_streams ();
+        Variant? upnp_class, file_info, dlna_profile, info, video_info, audio_info, meta_data;
 
-        if (audio_streams == null && video_streams == null) {
-            debug ("%s had neither audio nor video/picture " +
-                   "streams. Ignoring.",
-                   file.get_uri ());
+        var it = v.iterator ();
+        if (it.n_children () != 7) {
+            warning ("Invalid meta-data serialisation: exprected 7 children, got %d", (int) it.n_children 
());
 
             return null;
         }
 
-        if (audio_streams == null && video_streams.data.is_image ()) {
-            item = new PhotoItem (id, parent, "");
-            return fill_visual_item (item as PhotoItem,
-                                     file,
-                                     info,
-                                     profile,
-                                     video_streams.data,
-                                     file_info);
-        } else if (video_streams != null) {
-            item = new VideoItem (id, parent, "");
-
-            var audio_info = null as DiscovererAudioInfo;
-            if (audio_streams != null) {
-                audio_info = audio_streams.data;
-            }
+        var id = MediaCache.get_id (file);
 
-            return fill_video_item (item as VideoItem,
-                                    file,
-                                    info,
-                                    profile,
-                                    video_streams.data,
-                                    audio_info,
-                                    file_info);
-        } else if (audio_streams != null) {
-            item = new MusicItem (id, parent, "");
-            return fill_music_item (item as MusicItem,
-                                    file,
-                                    info,
-                                    profile,
-                                    audio_streams.data,
-                                    file_info);
-        } else {
-            return null;
+        upnp_class = it.next_value ();
+
+        file_info = it.next_value ().get_maybe ();
+        if (file_info != null) {
+            file_info = file_info.get_variant ();
         }
-    }
 
-    private static void fill_audio_item (AudioItem            item,
-                                         DiscovererInfo       info,
-                                         DiscovererAudioInfo? audio_info) {
-        if (info.get_duration () > 0) {
-            item.duration = (long) (info.get_duration () / Gst.SECOND);
-        } else {
-            item.duration = -1;
+        dlna_profile = it.next_value ().get_maybe ();
+        if (dlna_profile != null) {
+            dlna_profile = dlna_profile.get_variant ();
         }
 
-        if (audio_info == null) {
-            return;
+        info = it.next_value ().get_maybe ();
+        if (info != null) {
+            info = info.get_variant ();
         }
-  
-        var tags = audio_info.get_tags ();
-        if (tags != null) {
-            uint tmp;
-            tags.get_uint (Tags.BITRATE, out tmp);
-            item.bitrate = (int) tmp / 8;
+
+        audio_info = it.next_value ().get_maybe ();
+        if (audio_info != null) {
+            audio_info = audio_info.get_variant ();
         }
 
-        item.channels = (int) audio_info.get_channels ();
-        item.sample_freq = (int) audio_info.get_sample_rate ();
-    }
+        video_info = it.next_value ().get_maybe ();
+        if (video_info != null) {
+            video_info = video_info.get_variant ();
+        }
 
+        meta_data = it.next_value ().get_maybe ();
+        if (meta_data != null) {
+            meta_data = meta_data.get_variant ();
+        }
 
-    private static MediaFileItem fill_video_item (VideoItem            item,
-                                                  File                 file,
-                                                  DiscovererInfo       info,
-                                                  GUPnPDLNA.Profile?   profile,
-                                                  DiscovererVideoInfo  video_info,
-                                                  DiscovererAudioInfo? audio_info,
-                                                  FileInfo             file_info) {
-        fill_audio_item (item as AudioItem, info, audio_info);
-        fill_visual_item (item as VisualItem,
-                          file,
-                          info,
-                          profile,
-                          video_info,
-                          file_info);
+        MediaFileItem item = null;
+        switch (upnp_class.get_string ()) {
+            case Rygel.PhotoItem.UPNP_CLASS:
+                item = new PhotoItem (id, parent, "");
+                break;
+            case Rygel.VideoItem.UPNP_CLASS:
+                item = new VideoItem (id, parent, "");
+                break;
+            case Rygel.MusicItem.UPNP_CLASS:
+                item = new MusicItem (id, parent, "");
+                break;
+            default:
+                return null;
+        }
 
-        return item;
-    }
+        item.add_uri (file.get_uri ());
 
-    private static MediaFileItem fill_visual_item (VisualItem          item,
-                                                   File                file,
-                                                   DiscovererInfo      info,
-                                                   GUPnPDLNA.Profile?  profile,
-                                                   DiscovererVideoInfo video_info,
-                                                   FileInfo            file_info) {
-        fill_media_item (item, file, info, profile, file_info);
+        if (dlna_profile != null) {
+            apply_dlna_profile (item, dlna_profile);
+        }
 
-        item.width = (int) video_info.get_width ();
-        item.height = (int) video_info.get_height ();
+        if (file_info != null) {
+            apply_file_info (item, file_info);
+        }
 
-        var color_depth = (int) video_info.get_depth ();
-        item.color_depth = (color_depth == 0) ? -1 : color_depth;
+        if (info != null) {
+            apply_info (item, info);
+        }
 
-        return item;
-    }
+        if (audio_info != null) {
+            apply_audio_info (item, audio_info);
+        }
 
-    private static MediaFileItem fill_music_item (MusicItem            item,
-                                                  File                 file,
-                                                  DiscovererInfo       info,
-                                                  GUPnPDLNA.Profile?   profile,
-                                                  DiscovererAudioInfo? audio_info,
-                                                  FileInfo             file_info) {
-        fill_audio_item (item as AudioItem, info, audio_info);
-        fill_media_item (item, file, info, profile, file_info);
+        if (video_info != null) {
+            apply_video_info (item, video_info);
+        }
 
-        if (audio_info == null) {
-            return item;
+        if (meta_data != null) {
+            apply_meta_data (item, meta_data);
         }
 
-        var tags = audio_info.get_tags ();
-        if (tags == null) {
-            return item;
+        // If the date has a timezone offset, make sure it contains a
+        // colon bgo#702231, DLNA 7.3.21.1
+        if ("T" in item.date) {
+            var date = new Soup.Date.from_string (item.date);
+            item.date = date.to_string (Soup.DateFormat.ISO8601_FULL);
+        }
+
+        return item as MediaFileItem;
+    }
+
+    private static void apply_meta_data (MediaFileItem item, Variant v) {
+        if (!v.is_of_type (new VariantType ("(msmsmsiii)"))) {
+            warning ("Invalid meta-data serialisation of meta-data; %s",
+                     v.get_type_string ());
+
+            return;
         }
 
-        string artist;
-        tags.get_string (Tags.ARTIST, out artist);
-        item.artist = artist;
+        var it = v.iterator ();
+        var val = it.next_value ().get_maybe ();
+        item.artist = val == null ? null : val.dup_string ();
 
-        string album;
-        tags.get_string (Tags.ALBUM, out album);
-        item.album = album;
+        // Audio item
+        val = it.next_value ().get_maybe (); // album
+        var album = val == null ? null : val.dup_string ();
 
-        string genre;
-        tags.get_string (Tags.GENRE, out genre);
-        item.genre = genre;
+        val = it.next_value ().get_maybe ();
+        item.genre = val == null ? null : val.dup_string ();
 
-        uint tmp;
-        tags.get_uint (Tags.ALBUM_VOLUME_NUMBER, out tmp);
-        item.disc = (int) tmp;
+        // Audio item
+        var disc = it.next_value ().get_int32 ();
 
-        tags.get_uint (Tags.TRACK_NUMBER, out tmp);
-        item.track_number = (int) tmp;
+        if (item is AudioItem) {
+            var audio_item = item as AudioItem;
+            var track_number = it.next_value ().get_int32 ();
+            audio_item.bitrate = it.next_value ().get_int32 ();
+            audio_item.album = album;
 
+            if (item is MusicItem) {
+                var music_item = item as MusicItem;
+                music_item.disc = disc;
+                music_item.track_number = track_number;
+            }
+        }
+    }
 
-        var store = MediaArtStore.get_default ();
+    private static void apply_video_info (MediaFileItem item, Variant v) {
+        if (!v.is_of_type (new VariantType ("(iii)"))) {
+            warning ("Invalid meta-data serialisation of video info; %s",
+                     v.get_type_string ());
 
-        Sample sample;
-        tags.get_sample (Tags.IMAGE, out sample);
-        if (sample == null) {
-            tags.get_sample (Tags.PREVIEW_IMAGE, out sample);
+            return;
         }
 
-        if (sample == null) {
-            store.search_media_art_for_file (item, file);
+        if (!(item is VisualItem)) {
+            return;
+        }
 
-            return item;
+        var visual_item = item as VisualItem;
+        var it = v.iterator ();
+        visual_item.width = it.next_value ().get_int32 ();
+        visual_item.height = it.next_value ().get_int32 ();
+        visual_item.color_depth = it.next_value ().get_int32 ();
+    }
+
+    private static void apply_audio_info (MediaFileItem item, Variant v) {
+        if (!v.is_of_type (new VariantType ("(ii)"))) {
+            warning ("Invalid meta-data serialisation of audio info; %s",
+                     v.get_type_string ());
+
+            return;
         }
 
-        unowned Structure structure = sample.get_caps ().get_structure (0);
+        if (!(item is AudioItem)) {
+            return;
+        }
 
-        int image_type;
-        structure.get_enum ("image-type",
-                            typeof (Gst.Tag.ImageType),
-                            out image_type);
-        switch (image_type) {
-            case Tag.ImageType.UNDEFINED:
-            case Tag.ImageType.FRONT_COVER:
-                Gst.MapInfo map_info;
-                sample.get_buffer ().map (out map_info, Gst.MapFlags.READ);
+        var audio_item = item as AudioItem;
+        var it = v.iterator ();
+        audio_item.channels = it.next_value ().get_int32 ();
+        audio_item.sample_freq = it.next_value ().get_int32 ();
+    }
 
-                // Work-around bgo#739915
-                weak uint8[] data = map_info.data;
-                data.length = (int) map_info.size;
+    private static void apply_info (MediaFileItem item, Variant v) {
+        if (!v.is_of_type (new VariantType ("(msmsi)"))) {
+            warning ("Invalid meta-data serialisation of general info");
+        }
 
-                store.add (item, file, data, structure.get_name ());
-                sample.get_buffer ().unmap (map_info);
-                break;
-            default:
-                break;
+        var it = v.iterator ();
+        var val = it.next_value ().get_maybe ();
+        if (val != null) {
+            item.title = val.dup_string ();
         }
 
-        return item;
+        val = it.next_value ().get_maybe ();
+        if (val != null) {
+            item.date = val.dup_string ();
+        }
+
+        if (item is AudioItem) {
+            (item as AudioItem).duration = it.next_value ().get_int32 ();
+        }
     }
 
-    private static void fill_media_item (MediaFileItem      item,
-                                         File               file,
-                                         DiscovererInfo     info,
-                                         GUPnPDLNA.Profile? profile,
-                                         FileInfo           file_info) {
-        string title = null;
-
-        var tags = info.get_tags ();
-        if (tags == null ||
-            !tags.get_string (Tags.TITLE, out title)) {
-            title = file_info.get_display_name ();
-
-        }
-
-        // This assumes the datetime is valid; checking some demuxers this
-        Gst.DateTime? dt = null;
-        if (tags != null && tags.get_date_time (Tags.DATE_TIME, out dt)) {
-            // Make a minimal valid iso8601 date - bgo#702231
-            // This mostly happens with MP3 files which only have a year
-            if (!dt.has_day () || !dt.has_month ()) {
-                item.date = "%d-%02d-%02d".printf (dt.get_year (),
-                                                   dt.has_month () ?
-                                                       dt.get_month () : 1,
-                                                   dt.has_day () ?
-                                                       dt.get_day () : 1);
-            } else {
-                item.date = dt.to_iso8601_string ();
-            }
+    private static void apply_dlna_profile (MediaFileItem item, Variant v) {
+        if (!v.is_of_type (new VariantType ("(ss)"))) {
+            warning ("Invalid meta-data serialisation of DLNA profile %s",
+                     v.get_type_string ());
+
+            return;
         }
 
-        item.title = title;
+        var it = v.iterator ();
+        item.dlna_profile = it.next_value ().dup_string ();
+        item.mime_type = it.next_value ().dup_string ();
+    }
 
-        // use mtime if no time tag was available
-        var mtime = file_info.get_attribute_uint64
-                                        (FileAttribute.TIME_MODIFIED);
+    private static void apply_file_info (MediaFileItem item, Variant v) {
+        if (!v.is_of_type (new VariantType ("(sstt)"))) {
+            warning ("Invalid meta-data serialisation of file info %s",
+                     v.get_type_string ());
 
-        if (item.date == null) {
-            TimeVal tv = { (long) mtime, 0 };
-            item.date = tv.to_iso8601 ();
+            return;
         }
 
-        // If the date has a timezone offset, make sure it contains a
-        // colon bgo#702231, DLNA 7.3.21.1
-        if ("T" in item.date) {
-            var date = new Soup.Date.from_string (item.date);
-            item.date = date.to_string (Soup.DateFormat.ISO8601_FULL);
+        var it = v.iterator ();
+        if (it.n_children () != 4) {
+            warning ("Invalid meta-data serialisation of file info");
+
+            return;
         }
 
-        item.size = (int64) file_info.get_size ();
-        item.modified = (int64) mtime;
-        if (profile != null && profile.name != null) {
-            item.dlna_profile = profile.name;
-            item.mime_type = profile.mime;
-        } else {
-            item.mime_type = ContentType.get_mime_type
-                                        (file_info.get_content_type ());
+        Variant display_name;
+        display_name = it.next_value ();
+        if (item.title == null || item.title == "") {
+            item.title = display_name.dup_string ();
         }
 
-        item.add_uri (file.get_uri ());
+        var mime = it.next_value ();
+        if (item.mime_type == null) {
+            item.mime_type = mime.dup_string ();
+        }
+
+        item.modified = (int64) it.next_value ().get_uint64 ();
+        if (item.date == null) {
+            TimeVal tv = { (long) item.modified, 0 };
+            item.date = tv.to_iso8601 ();
+        }
+        item.size = (int64) it.next_value ().get_uint64 ();
     }
 }
diff --git a/src/plugins/media-export/rygel-media-export-metadata-extractor.vala 
b/src/plugins/media-export/rygel-media-export-metadata-extractor.vala
index 4d00778..1a47de0 100644
--- a/src/plugins/media-export/rygel-media-export-metadata-extractor.vala
+++ b/src/plugins/media-export/rygel-media-export-metadata-extractor.vala
@@ -29,133 +29,261 @@ using Gee;
 using GUPnP;
 using GUPnPDLNA;
 
+public errordomain MetadataExtractorError {
+    GENERAL,
+    BLACKLIST
+}
+
 /**
  * Metadata extractor based on Gstreamer. Just set the URI of the media on the
  * uri property, it will extact the metadata for you and emit signal
  * metadata_available for each key/value pair extracted.
  */
 public class Rygel.MediaExport.MetadataExtractor: GLib.Object {
+    private static VariantType SERIALIZED_DATA_TYPE;
+
     /* Signals */
-    public signal void extraction_done (File               file,
-                                        DiscovererInfo?    info,
-                                        GUPnPDLNA.Profile? profile,
-                                        FileInfo           file_info);
+    public signal void extraction_done (File file, Variant info);
 
     /**
      * Signalize that an error occured during metadata extraction
      */
     public signal void error (File file, Error err);
 
-    private Discoverer discoverer;
-    private ProfileGuesser guesser;
+    /// Cache for the config value
+    private bool extract_metadata;
 
-    /**
-     * We export a GLib.File-based API but GstDiscoverer works with URIs, so
-     * we store uri->GLib.File mappings in this hashmap, so that we can get
-     * the GLib.File back from the URI in on_discovered().
-     */
-    private HashMap<string, File> file_hash;
-    private uint timeout = 10; /* seconds */
+    /// Stream for feeding input to the child process.
+    private UnixOutputStream input_stream;
 
-    private bool extract_metadata;
+    /// Stream for receiving normal input from the child
+    private DataInputStream output_stream;
+
+    /// Stream for receiving exception events from the child
+    private DataInputStream error_stream;
+
+    /// Cancellable for cancelling child I/O
+    private Cancellable child_io_cancellable;
+
+    /// Launcher for subprocesses
+    private SubprocessLauncher launcher;
+
+    /// URI that caused a fatal error in the extraction process
+    private string error_uri = null;
+
+    [CCode (cheader_filename = "glib-unix.h", cname = "g_unix_open_pipe")]
+    extern static bool open_pipe ([CCode (array_length = false)]int[] fds, int flags) throws GLib.Error;
+
+    static construct {
+        SERIALIZED_DATA_TYPE = new VariantType ("(smvmvmvmvmvmv)");
+    }
 
     public MetadataExtractor () {
-        this.file_hash = new HashMap<string, File> ();
+        this.child_io_cancellable = new Cancellable ();
 
         var config = MetaConfig.get_default ();
         config.setting_changed.connect (this.on_config_changed);
         this.on_config_changed (config, Plugin.NAME, "extract-metadata");
     }
 
-    public void extract (File file, string content_type) {
-        if (this.extract_metadata && !content_type.has_prefix ("text/")) {
-            string uri = file.get_uri ();
+    [CCode (cname="MX_EXTRACT_PATH")]
+    private extern const string MX_EXTRACT_PATH;
+
+    private const string[] MX_EXTRACT_ARGV = {
+        MX_EXTRACT_PATH,
+        "--input-fd=3",
+        "--output-fd=4",
+        "--error-fd=5",
+        null
+    };
+
+    public void stop () {
+        this.child_io_cancellable.cancel ();
+        try {
+            var s = "QUIT\n";
+            this.input_stream.write_all (s.data, null, null);
+            this.input_stream.flush ();
+        } catch (Error error) {
+            warning (_("Failed to gracefully stop the process. Using KILL"));
+        }
+    }
+
+    public async void run () {
+        // We use dedicated fds for all of the communication, otherwise the
+        // commands/responses intermix with the debug output.
+        //
+        // This is still wip, we could also use a domain socket or a private
+        // DBus
+
+        int[] pipe_in = { 0, 0 };
+        int[] pipe_out = { 0, 0 };
+        int[] pipe_err = { 0, 0 };
+
+        bool restart = false;
+        do {
+            restart = false;
             try {
-                var gst_timeout = (ClockTime) (this.timeout * Gst.SECOND);
+                open_pipe (pipe_in, Posix.FD_CLOEXEC);
+                open_pipe (pipe_out, Posix.FD_CLOEXEC);
+                open_pipe (pipe_err, Posix.FD_CLOEXEC);
+
+                this.launcher = new SubprocessLauncher (SubprocessFlags.NONE);
+                this.launcher.take_fd (pipe_in[0], 3);
+                this.launcher.take_fd (pipe_out[1], 4);
+                this.launcher.take_fd (pipe_err[1], 5);
+
+                this.input_stream = new UnixOutputStream (pipe_in[1], true);
+                this.output_stream = new DataInputStream (
+                                                new UnixInputStream (pipe_out[0],
+                                                                     true));
+                this.error_stream = new DataInputStream (
+                                                new UnixInputStream (pipe_err[0],
+                                                                     true));
+
+                this.child_io_cancellable = new Cancellable ();
+
+                this.output_stream.read_line_async.begin (Priority.DEFAULT,
+                                                          this.child_io_cancellable,
+                                                          this.on_input);
+                this.error_uri = null;
+                this.error_stream.read_line_async.begin (Priority.DEFAULT,
+                                                         this.child_io_cancellable,
+                                                         this.on_child_error);
 
-                this.discoverer = new Discoverer (gst_timeout);
+                var subprocess = launcher.spawnv (MX_EXTRACT_ARGV);
+                try {
+                    yield subprocess.wait_check_async ();
+                    // Process exitted properly -> That shouldn't really
+                    // happen
+                } catch (Error error) {
+                    warning (_("Process check_async failed: %s"),
+                            error.message);
+
+                    // TODO: Handle error/crash/signal etc.
+                    restart = true;
+                    this.child_io_cancellable.cancel ();
+                    var msg = _("Process died while handling URI %s");
+                    this.error (File.new_for_uri (this.error_uri),
+                                new MetadataExtractorError.BLACKLIST (msg,
+                                                                      this.error_uri));
+                }
             } catch (Error error) {
-                debug ("Failed to create a discoverer. Doing basic extraction.");
-                this.extract_basic_information (file, null, null);
+                warning (_("Setting up extraction suprocess failed: %s"),
+                         error.message);
+            }
+        } while (restart);
+
+        debug ("Metadata extractor finished.");
+    }
 
-                return;
+    private void on_child_error (GLib.Object? object, AsyncResult result) {
+        var stream = object as DataInputStream;
+        if (stream != null) {
+            try {
+                this.error_uri = stream.read_line_async.end (result);
+                warning (_("Child failed fatally. Last uri was %s"),
+                         this.error_uri);
+            } catch (Error error) {
+                if (error is IOError.CANCELLED) {
+                    debug ("Reading was cancelled...");
+                } else {
+                    warning (_("Reading from child's error stream failed: %s"),
+                             error.message);
+                }
             }
-            this.file_hash.set (uri, file);
-            this.discoverer.discovered.connect (this.on_done);
-            this.discoverer.start ();
-            this.discoverer.discover_uri_async (uri);
-            this.guesser = new GUPnPDLNA.ProfileGuesser (true, true);
-        } else {
-            this.extract_basic_information (file, null, null);
         }
     }
 
-    private void on_done (DiscovererInfo info, GLib.Error err) {
-        this.discoverer = null;
-        var file = this.file_hash.get (info.get_uri ());
-        if (file == null) {
-            warning ("File %s already handled, ignoring event",
-                     info.get_uri ());
+    private void on_input (GLib.Object? object, AsyncResult result) {
+        try {
+            var stream = object as DataInputStream;
+            var str = stream.read_line_async.end (result);
 
-            return;
-        }
+            // XXX: While and Goto language are equivalent. Yuck.
+            do {
+                if (str == null) {
+                    break;
+                }
 
-        this.file_hash.unset (info.get_uri ());
+                if (!str.has_prefix ("RESULT|") &&
+                    !str.has_prefix ("ERROR|")) {
+                    warning (_("Received invalid string from child: %s"), str);
 
-        if (info.get_result () == DiscovererResult.ERROR ||
-            info.get_result () == DiscovererResult.URI_INVALID) {
-            this.error (file, err);
+                    break;
+                }
 
-            return;
-        } else if (info.get_result () == DiscovererResult.TIMEOUT ||
-                   info.get_result () == DiscovererResult.BUSY ||
-                   info.get_result () == DiscovererResult.MISSING_PLUGINS) {
-            if (info.get_result () == DiscovererResult.MISSING_PLUGINS) {
-                debug ("Plugins are missing for extraction of file %s",
-                       file.get_uri ());
-            } else {
-                debug ("Extraction timed out on %s", file.get_uri ());
-            }
-            this.extract_basic_information (file, null, null);
+                var parts = str.split ("|");
+                if (parts.length != 4) {
+                    warning (_("Received ill-formed response string %s from child…"),
+                             str);
 
-            return;
-        }
+                    break;
+                }
 
-        var dlna_info = GUPnPDLNAGst.utils_information_from_discoverer_info (info);
-        var dlna = this.guesser.guess_profile_from_info (dlna_info);
-        this.extract_basic_information (file, info, dlna);
-    }
+                if (parts[0] == "ERROR") {
+                    this.error (File.new_for_uri (parts[1]),
+                                new MetadataExtractorError.GENERAL (parts[3]));
 
-    private void extract_basic_information (File               file,
-                                            DiscovererInfo?    info,
-                                            GUPnPDLNA.Profile? dlna) {
-        FileInfo file_info;
+                    break;
+                }
 
-        try {
-            file_info = file.query_info (FileAttribute.STANDARD_CONTENT_TYPE
-                                         + "," +
-                                         FileAttribute.STANDARD_SIZE + "," +
-                                         FileAttribute.TIME_MODIFIED + "," +
-                                         FileAttribute.STANDARD_DISPLAY_NAME,
-                                         FileQueryInfoFlags.NONE,
-                                         null);
+                var uri = parts[1];
+                var length = uint64.parse (parts[2]);
+
+                debug ("Found serialized data for uri %s", uri);
+                var buf = new uint8[length];
+                size_t bytes;
+                this.output_stream.read_all (buf,
+                                             out bytes,
+                                             this.child_io_cancellable);
+                debug ("Expected %" + size_t.FORMAT + " bytes, got %" +
+                       size_t.FORMAT,
+                       length,
+                       bytes);
+
+                var v = Variant.new_from_data<void> (SERIALIZED_DATA_TYPE,
+                                                     (uchar[]) buf,
+                                                     true);
+                this.extraction_done (File.new_for_uri (uri), v);
+            } while (false);
+
+            this.output_stream.read_line_async.begin (Priority.DEFAULT,
+                                                      this.child_io_cancellable,
+                                                      this.on_input);
         } catch (Error error) {
-            var uri = file.get_uri ();
+            if (error is IOError.CANCELLED) {
+                debug ("Read was cancelled, process probably died…");
+                // No error signalling, this was done in the part that called
+                // cancel
+            } else {
+                warning (_("Read from child failed: %s"), error.message);
+                this.error (File.new_for_uri (this.error_uri),
+                            new MetadataExtractorError.GENERAL ("Failed"));
+
+            }
+        }
+    }
 
-            warning (_("Failed to extract basic metadata from %s: %s"),
-                     uri,
-                     error.message);
+    public void extract (File file, string content_type) {
+        if (this.child_io_cancellable.is_cancelled ()) {
+            debug ("Child apparently already died, scheduling command for later");
+            Idle.add (() => {
+                this.extract (file, content_type);
 
-            // signal error to parent
-            this.error (file, error);
+                return false;
+            });
 
             return;
         }
 
-        this.extraction_done (file,
-                              info,
-                              dlna,
-                              file_info);
+        var s = "EXTRACT %s\n".printf (file.get_uri ());
+        try {
+            this.input_stream.write_all (s.data, null, this.child_io_cancellable);
+            this.input_stream.flush ();
+            debug ("Sent command to extractor process: %s", s);
+        } catch (Error error) {
+            warning (_("Failed to send command to child: %s"), error.message);
+        }
     }
 
     private void on_config_changed (Configuration config,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]