[rygel] media-export: Add media harvester class



commit b0c7c60af392db2258d009f6d0d90dfecdf87cb9
Author: Jens Georg <mail jensge org>
Date:   Wed Jun 24 22:43:40 2009 +0200

    media-export: Add media harvester class

 src/plugins/media-export/Makefile.am               |    3 +
 .../media-export/rygel-media-export-harvester.vala |  261 ++++++++++++++++++++
 .../media-export/rygel-media-export-item.vala      |  136 ++++++++++
 .../rygel-media-export-root-container.vala         |   98 ++++----
 4 files changed, 453 insertions(+), 45 deletions(-)
---
diff --git a/src/plugins/media-export/Makefile.am b/src/plugins/media-export/Makefile.am
index 8f7fa51..d986c4f 100644
--- a/src/plugins/media-export/Makefile.am
+++ b/src/plugins/media-export/Makefile.am
@@ -13,6 +13,7 @@ AM_CFLAGS = $(LIBGUPNP_CFLAGS) \
 BUILT_SOURCES = rygel-media-export-root-container.c \
 		rygel-media-export-container.c \
 	        rygel-media-export-item.c \
+		rygel-media-export-harvester.c \
 		rygel-media-export-directory-search-result.c \
 		rygel-media-export-plugin.c
 
@@ -23,6 +24,8 @@ librygel_media_export_la_SOURCES = \
 	rygel-media-export-plugin.vala \
 	rygel-media-export-root-container.c \
 	rygel-media-export-root-container.vala \
+	rygel-media-export-harvester.c \
+	rygel-media-export-harvester.vala \
 	rygel-media-export-container.c \
 	rygel-media-export-container.vala \
 	rygel-media-export-item.c \
diff --git a/src/plugins/media-export/rygel-media-export-harvester.vala b/src/plugins/media-export/rygel-media-export-harvester.vala
new file mode 100644
index 0000000..7b828e8
--- /dev/null
+++ b/src/plugins/media-export/rygel-media-export-harvester.vala
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2009 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 GLib;
+using Gee;
+using Rygel;
+
+internal class DummyContainer : Rygel.MediaContainer {
+    public DummyContainer (File file, MediaContainer parent) {
+        var id = Checksum.compute_for_string (ChecksumType.MD5,
+                                              file.get_uri ());
+        base (id, parent, file.get_basename (), 0);
+        this.parent_ref = parent;
+    }
+
+
+    public override void get_children (uint               offset,
+                                       uint               max_count,
+                                       Cancellable?       cancellable,
+                                       AsyncReadyCallback callback) {}
+
+    public override Gee.List<MediaObject>? get_children_finish (
+                                                    AsyncResult res)
+                                                    throws Error
+                                                    { return null; }
+
+    public override void find_object (string             id,
+                                      Cancellable?       cancellable,
+                                      AsyncReadyCallback callback) { }
+
+    public override MediaObject? find_object_finish (AsyncResult res)
+                                                     throws Error { return
+                                                     null;}
+
+}
+
+public class Rygel.MediaExportHarvester : GLib.Object {
+    private MetadataExtractor extractor;
+    private MediaDB media_db;
+    private Queue<File> directories;
+    private Queue<MediaContainer> containers;
+    private Queue<File> files;
+    private File origin;
+    private MediaContainer parent;
+
+    private void dump_queues () {
+        print ("directories queue\n==================\n");
+        for (int i = 0; i < directories.length; i++) {
+            print ("\t%s\n", directories.peek_nth (i).get_basename ());
+        }
+
+        print ("files queue\n==================\n");
+        for (int i = 0; i < files.length; i++) {
+            print ("\t%s\n", files.peek_nth (i).get_basename ());
+        }
+
+        print ("container queue\n==================\n");
+        for (int i = 0; i < containers.length; i++) {
+            print ("\t%s\n", containers.peek_nth (i).title);
+        }
+    }
+
+    public MediaExportHarvester (MediaContainer parent,
+                                 MediaDB media_db,
+                                 MetadataExtractor extractor) {
+        this.parent = parent;
+        this.extractor = extractor;
+        this.media_db = media_db;
+        this.extractor.extraction_done.connect (on_extracted_cb);
+        this.extractor.error.connect (on_extractor_error_cb);
+        this.directories = new Queue<File> ();
+        this.files = new Queue<File> ();
+        this.containers = new Queue<MediaContainer> ();
+        this.origin = null;
+        Idle.add (this.on_idle);
+    }
+
+    private void on_close_async (Object obj, AsyncResult res) {
+        var enumerator = (FileEnumerator) obj;
+        try {
+            enumerator.close_finish (res);
+        } catch (Error error) {
+            // TODO
+        }
+        this.directories.pop_head ();
+        if (this.files.get_length() == 0 && this.containers.get_length () != 0) {
+            this.containers.pop_head ();
+        }
+
+        Idle.add(this.on_idle);
+    }
+
+    private void on_next_files_ready (Object obj, AsyncResult res) {
+        var enumerator = (FileEnumerator) obj;
+        try {
+            var list = enumerator.next_files_finish (res);
+            if (list != null) {
+                foreach (var info in list) {
+                    var dir = this.directories.peek_head ();
+                    var file = dir.get_child (info.get_name ());
+                    if (info.get_file_type () == FileType.DIRECTORY) {
+                        this.directories.push_tail (file);
+                        this.containers.push_tail (
+                            new DummyContainer (file,
+                               this.containers.peek_head ()));
+                        this.media_db.save_object (this.containers.peek_tail ());
+                    } else {
+                        this.files.push_tail (file);
+                    }
+                    dump_queues ();
+                }
+
+                enumerator.next_files_async (10,
+                                             Priority.DEFAULT,
+                                             null,
+                                             this.on_next_files_ready);
+            } else {
+                enumerator.close_async (Priority.DEFAULT,
+                                        null,
+                                        this.on_close_async);
+            }
+        } catch (Error error) {
+            // TODO
+        }
+    }
+
+    private void on_enumerate_ready (Object obj, AsyncResult res) {
+        var file = (File) obj;
+        try {
+            var enumerator = file.enumerate_children_finish (res);
+            enumerator.next_files_async (10,
+                                         Priority.DEFAULT,
+                                         null,
+                                         on_next_files_ready);
+        } catch (Error error) {
+            // TODO
+        }
+    }
+
+    private bool on_idle () {
+        dump_queues ();
+        if (this.files.get_length () > 0) {
+            var candidate = this.files.peek_head ();
+            this.extractor.extract (candidate);
+        } else if (this.directories.get_length () > 0) {
+            var directory = this.directories.peek_head ();
+            directory.enumerate_children_async (
+                            FILE_ATTRIBUTE_STANDARD_TYPE + "," +
+                            FILE_ATTRIBUTE_STANDARD_NAME,
+                            FileQueryInfoFlags.NONE,
+                            Priority.DEFAULT,
+                            null,
+                            this.on_enumerate_ready);
+        } else {
+            // nothing to do
+            harvested (this.origin);
+            this.origin = null;
+        }
+
+        return false;
+    }
+
+    /**
+     * Fired for every file passed to harvest.
+     */
+    public signal void harvested (File file);
+
+    /**
+     * Extract all metainformation from a given file.
+     *
+     * What action will be taken depends on the arguments
+     * * file is a simple file. Then only information of this
+     *   file will be extracted
+     * * file is a directory and recursive is false. The children
+     *   of the directory (if not directories themselves) will be
+     *   enqueued for extraction
+     * * file is a directory and recursive is true. ++ All ++ children
+     *   of the directory will be enqueued for extraction, even directories
+     *
+     * No matter how many children are contained within file's hierarchy,
+     * only one event is sent when all the children are done.
+     */
+    public void harvest (File file) {
+        if (file.query_exists (null)) {
+            try {
+                var info = file.query_info (
+                             FILE_ATTRIBUTE_STANDARD_NAME + "," +
+                             FILE_ATTRIBUTE_STANDARD_TYPE,
+                             FileQueryInfoFlags.NONE,
+                             null);
+                if (info.get_file_type () == FileType.DIRECTORY) {
+                    this.origin = file;
+                    this.directories.push_tail (file);
+                    this.containers.push_tail (
+                                new DummyContainer (file,
+                                                          this.parent));
+
+                    this.media_db.save_object (this.containers.peek_tail ());
+                } else {
+                    this.origin = file;
+                    this.files.push_tail (file);
+                    this.containers.push_tail (this.parent);
+                }
+            } catch (Error error) {
+                debug ("Failed to query info for file %s: %s",
+                       file.get_uri (),
+                       error.message);
+            }
+        }
+    }
+
+    private void on_extracted_cb (File file, Gst.TagList tag_list) {
+        if (file == this.files.peek_head ()) {
+            debug ("successfully harvested file %s", file.get_uri ());
+
+            var item = MediaExportItem.create_from_taglist (
+                                               this.containers.peek_head (),
+                                               file,
+                                               tag_list);
+            item.parent_ref = this.containers.peek_head ();
+            this.media_db.save_object (item);
+
+            this.files.pop_head ();
+            if (this.files.get_length () == 0 &&
+                this.containers.get_length () != 0) {
+                this.containers.pop_head ();
+            }
+            Idle.add(this.on_idle);
+        }
+    }
+
+    private void on_extractor_error_cb (File file, Error error) {
+        if (file == this.files.peek_head ()) {
+            debug ("failed to harvest file %s", file.get_uri ());
+            // yadda yadda
+            this.files.pop_head ();
+            if (this.files.get_length () == 0 &&
+                this.containers.get_length () != 0) {
+                this.containers.pop_head ();
+            }
+            Idle.add(this.on_idle);
+        }
+    }
+}
diff --git a/src/plugins/media-export/rygel-media-export-item.vala b/src/plugins/media-export/rygel-media-export-item.vala
index 7b0a4fa..890d4a5 100644
--- a/src/plugins/media-export/rygel-media-export-item.vala
+++ b/src/plugins/media-export/rygel-media-export-item.vala
@@ -22,6 +22,7 @@
  */
 
 using GUPnP;
+using Gst;
 
 /**
  * Represents MediaExport item.
@@ -57,5 +58,140 @@ public class Rygel.MediaExportItem : MediaItem {
         this.mime_type = content_type;
         this.uris.add (file.get_uri ());
     }
+
+    private void fill_from_tags_as_image (Gst.TagList tag_list) {
+
+        tag_list.get_string (MetadataExtractor.TAG_RYGEL_MIME, out this.mime_type);
+        int64 size;
+        tag_list.get_int64 (MetadataExtractor.TAG_RYGEL_SIZE, out size);
+        this.size = (long) size;
+        tag_list.get_int (MetadataExtractor.TAG_RYGEL_WIDTH, out this.width);
+        tag_list.get_int (MetadataExtractor.TAG_RYGEL_HEIGHT, out this.height);
+        tag_list.get_int (MetadataExtractor.TAG_RYGEL_DEPTH, out this.color_depth);
+    }
+
+    private void fill_from_tags_as_audio (Gst.TagList tag_list) {
+        int64 duration;
+        tag_list.get_int64 (MetadataExtractor.TAG_RYGEL_DURATION, out duration);
+        this.duration = (long) (duration / 1000000000);
+
+        tag_list.get_int (MetadataExtractor.TAG_RYGEL_CHANNELS, out this.n_audio_channels);
+        tag_list.get_string (MetadataExtractor.TAG_RYGEL_MIME, out this.mime_type);
+
+        int64 size;
+        tag_list.get_int64 (MetadataExtractor.TAG_RYGEL_SIZE, out size);
+        this.size = (long) size;
+
+        tag_list.get_string (TAG_ARTIST, out this.author);
+        tag_list.get_string (TAG_ALBUM, out this.album);
+
+        uint tmp;
+        tag_list.get_uint (TAG_TRACK_NUMBER, out tmp);
+        this.track_number = (int)tmp;
+        tag_list.get_uint (TAG_BITRATE, out tmp);
+        this.bitrate = (int)tmp;
+        tag_list.get_int (MetadataExtractor.TAG_RYGEL_RATE, out this.sample_freq);
+
+        GLib.Date? date;
+        if (tag_list.get_date (TAG_DATE, out date)) {
+            char[] datestr = new char[30];
+            date.strftime(datestr, "%F");
+            this.date = (string)datestr;
+        }
+    }
+
+    private void fill_from_tags_as_video (Gst.TagList tag_list) {
+        tag_list.get_string (MetadataExtractor.TAG_RYGEL_MIME,
+                out this.mime_type);
+        int64 size;
+        tag_list.get_int64 (MetadataExtractor.TAG_RYGEL_SIZE,
+                out size);
+        this.size = (long) size;
+        tag_list.get_int (MetadataExtractor.TAG_RYGEL_WIDTH,
+                out this.width);
+        tag_list.get_int (MetadataExtractor.TAG_RYGEL_HEIGHT,
+                out this.height);
+        tag_list.get_int (MetadataExtractor.TAG_RYGEL_DEPTH,
+                out this.color_depth);
+        tag_list.get_int (MetadataExtractor.TAG_RYGEL_CHANNELS,
+                out this.n_audio_channels);
+        tag_list.get_int (MetadataExtractor.TAG_RYGEL_RATE,
+                out this.sample_freq);
+    }
+
+    public static MediaItem? create_from_taglist (MediaContainer parent,
+                                                  File file,
+                                                  Gst.TagList tag_list) {
+        string id = Checksum.compute_for_string (ChecksumType.MD5,
+                                                 file.get_uri ());
+        int width = -1;
+        int height = -1;
+        string class_guessed = null;
+
+        if (tag_list != null) {
+            string codec;
+
+            if (!tag_list.get_string (TAG_VIDEO_CODEC, out codec)) {
+                if (!tag_list.get_string (TAG_AUDIO_CODEC, out codec)) {
+                    if (tag_list.get_int (MetadataExtractor.TAG_RYGEL_WIDTH, out width) ||
+                        tag_list.get_int (MetadataExtractor.TAG_RYGEL_HEIGHT, out height)) {
+                        class_guessed = MediaItem.IMAGE_CLASS;
+                    } else {
+                        warning("There's no codec inside and no image for file" +
+                                "%s", file.get_uri ());
+                    }
+                } else {
+                    class_guessed = MediaItem.AUDIO_CLASS;
+                }
+            } else {
+                class_guessed = MediaItem.VIDEO_CLASS;
+            }
+        } else {
+            // throw error. Taglist can't be empty
+            warning("Got empty taglist for file %s", file.get_uri ());
+            return null;
+        }
+
+        return new MediaExportItem.from_taglist (parent,
+                                                 id,
+                                                 file,
+                                                 tag_list,
+                                                 class_guessed);
+    }
+
+    private MediaExportItem.from_taglist (MediaContainer parent,
+                                          string id,
+                                          File file,
+                                          Gst.TagList tag_list,
+                                          string upnp_class) {
+        string title = null;
+        if (upnp_class == MediaItem.AUDIO_CLASS ||
+            upnp_class == MediaItem.MUSIC_CLASS) {
+
+            if (!tag_list.get_string (TAG_TITLE, out title)) {
+                title = file.get_basename ();
+            }
+
+        } else {
+            title = file.get_basename ();
+        }
+        base (id, parent, title, upnp_class);
+        switch (upnp_class) {
+            case MediaItem.AUDIO_CLASS:
+            case MediaItem.MUSIC_CLASS:
+                fill_from_tags_as_audio (tag_list);
+                break;
+            case MediaItem.VIDEO_CLASS:
+                fill_from_tags_as_video (tag_list);
+                break;
+            case MediaItem.IMAGE_CLASS:
+                fill_from_tags_as_image (tag_list);
+                break;
+            default:
+                break;
+        }
+
+        this.uris.add (file.get_uri ());
+    }
 }
 
diff --git a/src/plugins/media-export/rygel-media-export-root-container.vala b/src/plugins/media-export/rygel-media-export-root-container.vala
index 1f6ca2c..fdce4ff 100644
--- a/src/plugins/media-export/rygel-media-export-root-container.vala
+++ b/src/plugins/media-export/rygel-media-export-root-container.vala
@@ -24,64 +24,44 @@ using Gee;
  * Represents the root container.
  */
 public class Rygel.MediaExportRootContainer : MediaContainer {
-    private ArrayList<MediaObject> children;
+    private MediaDB media_db;
+    private Gee.ArrayList<MediaObject> children;
+    private DatabaseBackedMediaContainer root_container;
+    private MetadataExtractor extractor;
+    private Gee.ArrayList<MediaExportHarvester> harvester;
 
     public override void get_children (uint offset,
                                        uint max_count,
                                        Cancellable? cancellable,
                                        AsyncReadyCallback callback)
     {
-        uint stop = offset + max_count;
-        stop = stop.clamp (0, this.child_count);
-        var children = this.children.slice ((int) offset, (int) stop);
-        var res = new Rygel.SimpleAsyncResult<Gee.List<MediaObject>> (this,
-                                                                      callback);
-        res.data = children;
-        res.complete_in_idle ();
+        debug ("get children called");
+        this.root_container.get_children (offset,
+                                          max_count,
+                                          cancellable,
+                                          callback);
     }
 
     public override Gee.List<MediaObject>? get_children_finish (
                                                     AsyncResult res)
                                                     throws GLib.Error {
-        var simple_res = (Rygel.SimpleAsyncResult<Gee.List<MediaObject>>) res;
-
-        return simple_res.data;
+        debug ("get children_finish called");
+        return this.root_container.get_children_finish (res);
     }
 
     public override void find_object (string id,
                                       Cancellable? cancellable,
                                       AsyncReadyCallback callback) {
-        var res = new Rygel.SimpleAsyncResult<string> (this, callback);
-
-        res.data = id;
-        res.complete_in_idle ();
+        debug ("find object called");
+        this.root_container.find_object (id,
+                                         cancellable,
+                                         callback);
     }
 
     public override MediaObject? find_object_finish (AsyncResult res)
                                                      throws GLib.Error {
-        MediaObject media_obj = null;
-        var id = ((Rygel.SimpleAsyncResult<string>) res).data;
-
-        foreach (var tmp in this.children) {
-            if (id == tmp.id) {
-                media_obj = tmp;
-                break;
-            }
-        }
-
-        if (media_obj == null) {
-            foreach (var tmp in this.children) {
-                if (tmp is MediaExportContainer) {
-                    var folder = (MediaExportContainer) tmp;
-                    media_obj = folder.find_object_sync (id);
-                    if (media_obj != null) {
-                        break;
-                    }
-                }
-            }
-        }
-
-        return media_obj;
+        debug ("find object finish called");
+        return this.root_container.find_object_finish (res);
     }
 
     /**
@@ -89,6 +69,22 @@ public class Rygel.MediaExportRootContainer : MediaContainer {
      */
     public MediaExportRootContainer () {
         base.root ("MediaExportRoot", 0);
+        var media_db_path = Path.build_filename (
+                                            Environment.get_user_cache_dir (),
+                                            Environment.get_prgname (),
+                                            "media-export.db");
+
+        debug("Using media database %s", media_db_path);
+
+        this.media_db = new MediaDB(media_db_path);
+        this.extractor = new MetadataExtractor ();
+
+
+        this.root_container = new DatabaseBackedMediaContainer (this.media_db,
+                                                                "0",
+                                                                "MediaExportRoot");
+
+        this.harvester = new Gee.ArrayList<MediaExportHarvester> ();
         ArrayList<string> uris;
 
         this.children = new ArrayList<MediaExportContainer> ();
@@ -120,7 +116,11 @@ public class Rygel.MediaExportRootContainer : MediaContainer {
         foreach (var uri in uris) {
             var f = File.new_for_commandline_arg (uri);
             if (f.query_exists (null)) {
-                f.query_info_async (
+                var id = Checksum.compute_for_string (ChecksumType.MD5,
+                                                      uri);
+                var obj = media_db.get_object (id);
+                if (obj == null) {
+                    f.query_info_async (
                             FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE + "," +
                             FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME + "," +
                             FILE_ATTRIBUTE_STANDARD_TYPE + "," +
@@ -129,6 +129,10 @@ public class Rygel.MediaExportRootContainer : MediaContainer {
                             Priority.DEFAULT,
                             null,
                             this.on_info_ready);
+                } else {
+                    this.child_count++;
+                    this.updated ();
+                }
             }
         }
     }
@@ -141,15 +145,19 @@ public class Rygel.MediaExportRootContainer : MediaContainer {
             MediaObject media_obj = null;
 
             if (info.get_file_type () == FileType.DIRECTORY) {
-                media_obj = new MediaExportContainer (this, file);
+                media_obj = new MediaExportContainer (root_container, file);
             } else {
-                media_obj = new MediaExportItem (this, file, info);
+                media_obj = new MediaExportItem (root_container, file, info);
             }
 
-            this.children.add (media_obj);
-            this.child_count = this.children.size;
-
-            this.updated ();
+            if (media_obj != null) {
+                debug ("Scheduling new harvester");
+                var harvest =
+                    new MediaExportHarvester (this.root_container, media_db,
+                                              extractor);
+                this.harvester.add (harvest);
+                harvest.harvest (file);
+            }
         } catch (Error err) {
             warning ("Failed to query information on '%s': %s\n",
                      file.get_uri (),



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