[rygel] core,plugins: Proper, rich item hierarchy



commit aaf25fb2c2e79dac1d3ec43bad2b5dd58f541e0d
Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
Date:   Wed Aug 25 15:06:41 2010 +0300

    core,plugins: Proper, rich item hierarchy
    
    * We have the following item hierarchy now:
    
      * VisualItem (interface, requires MediaItem)
      * MediaItem (abstract class)
         |
         |--> AudioItem
         |      |
         |      |--> MusicItem
         |      |--> VideoItem (implements VisualItem)
         |
         |--> ImageItem (implements VisualItem)
                |
                |--> PhotoItem
    
    * Serialization of MediaItem is completely handled by MediaItem itself.

 po/POTFILES.in                                     |    8 +-
 po/POTFILES.skip                                   |    6 +
 .../external/rygel-external-item-factory.vala      |  106 +++++--
 src/plugins/gst-launch/Makefile.am                 |    4 +-
 .../rygel-gst-launch-audio-item.vala}              |   33 +-
 src/plugins/gst-launch/rygel-gst-launch-item.vala  |   30 +--
 .../rygel-gst-launch-root-container.vala           |   18 +-
 .../rygel-gst-launch-video-item.vala}              |   33 +-
 .../rygel-media-export-harvesting-task.vala        |    4 +-
 .../media-export/rygel-media-export-item.vala      |  337 ++++++++++----------
 .../rygel-media-export-media-cache.vala            |  102 +++++--
 .../rygel-media-export-object-factory.vala         |   11 +-
 .../mediathek/rygel-mediathek-video-item.vala      |    9 +-
 src/plugins/test/Makefile.am                       |    1 -
 src/plugins/test/rygel-test-audio-item.vala        |   10 +-
 src/plugins/test/rygel-test-video-item.vala        |   10 +-
 .../tracker/rygel-tracker-item-factory.vala        |   18 +-
 .../tracker/rygel-tracker-music-item-factory.vala  |   33 ++-
 .../rygel-tracker-picture-item-factory.vala        |   27 ++-
 .../tracker/rygel-tracker-video-item-factory.vala  |   25 +-
 src/rygel/Makefile.am                              |    6 +
 src/rygel/rygel-audio-item.vala                    |   67 ++++
 src/rygel/rygel-didl-lite-writer.vala              |   59 +----
 src/rygel/rygel-http-get-handler.vala              |    4 +-
 src/rygel/rygel-http-get.vala                      |   23 ++-
 src/rygel/rygel-http-server.vala                   |   53 +---
 src/rygel/rygel-http-time-seek.vala                |    5 +-
 src/rygel/rygel-image-item.vala                    |   99 ++++++
 src/rygel/rygel-item-creator.vala                  |   46 ++-
 src/rygel/rygel-l16-transcoder.vala                |   17 +-
 src/rygel/rygel-media-art-store.vala               |   22 +-
 src/rygel/rygel-media-container.vala               |    8 +-
 src/rygel/rygel-media-item.vala                    |  212 +++++--------
 src/rygel/rygel-mp2ts-transcoder.vala              |   29 +-
 src/rygel/rygel-mp3-transcoder.vala                |    9 +-
 src/rygel/rygel-music-item.vala                    |  129 ++++++++
 src/rygel/rygel-photo-item.vala                    |   67 ++++
 src/rygel/rygel-relational-expression.vala         |    6 +-
 src/rygel/rygel-transcode-manager.vala             |    5 +-
 src/rygel/rygel-transcoder.vala                    |    2 +-
 src/rygel/rygel-video-item.vala                    |  153 +++++++++
 src/rygel/rygel-visual-item.vala                   |   90 ++++++
 src/rygel/rygel-wma-transcoder.vala                |    9 +-
 src/rygel/rygel-wmv-transcoder.vala                |   15 +-
 44 files changed, 1304 insertions(+), 656 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0c7bc32..4e9f587 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -37,7 +37,7 @@ src/plugins/media-export/rygel-media-export-query-container.vala
 src/plugins/media-export/rygel-media-export-recursive-file-monitor.vala
 src/plugins/media-export/rygel-media-export-root-container.vala
 src/plugins/test/rygel-test-audio-item.vala
-src/plugins/test/rygel-test-item.vala
+src/plugins/test/rygel-test-video-item.vala
 src/plugins/test/rygel-test-plugin.vala
 src/plugins/test/rygel-test-root-container.vala
 src/plugins/test/rygel-test-video-item.vala
@@ -100,6 +100,12 @@ src/rygel/rygel-logical-expression.vala
 src/rygel/rygel-main.vala
 src/rygel/rygel-media-container.vala
 src/rygel/rygel-media-item.vala
+src/rygel/rygel-music-item.vala
+src/rygel/rygel-audio-item.vala
+src/rygel/rygel-image-item.vala
+src/rygel/rygel-photo-item.vala
+src/rygel/rygel-video-item.vala
+src/rygel/rygel-visual-item.vala
 src/rygel/rygel-media-object.vala
 src/rygel/rygel-media-receiver-registrar.vala
 src/rygel/rygel-meta-config.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index f6c4b45..c389cb3 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -59,6 +59,12 @@ src/rygel/rygel-log-handler.c
 src/rygel/rygel-main.c
 src/rygel/rygel-media-container.c
 src/rygel/rygel-media-item.c
+src/rygel/rygel-music-item.c
+src/rygel/rygel-audio-item.c
+src/rygel/rygel-image-item.c
+src/rygel/rygel-photo-item.c
+src/rygel/rygel-video-item.c
+src/rygel/rygel-visual-item.c
 src/rygel/rygel-meta-config.c
 src/rygel/rygel-metadata-extractor.c
 src/rygel/rygel-mp2ts-transcoder-bin.c
diff --git a/src/plugins/external/rygel-external-item-factory.vala b/src/plugins/external/rygel-external-item-factory.vala
index ce67df7..f83bca9 100644
--- a/src/plugins/external/rygel-external-item-factory.vala
+++ b/src/plugins/external/rygel-external-item-factory.vala
@@ -38,19 +38,38 @@ public class Rygel.External.ItemFactory {
                                    string                   host_ip,
                                    MediaContainer           parent)
                                    throws GLib.Error {
-        string upnp_class;
+        MediaItem item;
 
         if (type.has_prefix ("audio")) {
-            upnp_class = MediaItem.AUDIO_CLASS;
+            item = new AudioItem (id, parent, title);
+
+            this.set_audio_metadata (item as AudioItem,
+                                     props,
+                                     service_name,
+                                     host_ip);
         } else if (type.has_prefix ("music")) {
-            upnp_class = MediaItem.MUSIC_CLASS;
+            item = new MusicItem (id, parent, title);
+
+            yield this.set_music_metadata (item as MusicItem,
+                                           props,
+                                           service_name,
+                                           host_ip);
         } else if (type.has_prefix ("video")) {
-            upnp_class = MediaItem.VIDEO_CLASS;
+            item = new VideoItem (id, parent, title);
+
+            yield this.set_video_metadata (item as VideoItem,
+                                           props,
+                                           service_name,
+                                           host_ip);
         } else {
-            upnp_class = MediaItem.IMAGE_CLASS;
+            item = new ImageItem (id, parent, title);
+
+            yield this.set_visual_metadata (item as VisualItem,
+                                            props,
+                                            service_name,
+                                            host_ip);
         }
 
-        var item = new MediaItem (id, parent, title, upnp_class);
         if (parent is DummyContainer) {
             item.parent_ref = parent;
         }
@@ -75,45 +94,74 @@ public class Rygel.External.ItemFactory {
             item.size = (int64) value;
         }
 
-        item.author = this.get_string (props, "Artist");
-        item.album = this.get_string (props, "Album");
-        item.genre = this.get_string (props, "Genre");
         item.date = this.get_string (props, "Date");
 
-        // Properties specific to video and audio/music
-
-        item.duration = this.get_int (props, "Duration");
-        item.bitrate = this.get_int (props, "Bitrate");
-        item.sample_freq = this.get_int (props, "SampleRate");
-        item.bits_per_sample = this.get_int (props, "BitsPerSample");
+        return item;
+    }
 
-        value = props.lookup ("AlbumArt");
+    private async void set_music_metadata (
+                                        MusicItem                music,
+                                        HashTable<string,Value?> props,
+                                        string                   service_name,
+                                        string                   host_ip)
+                                        throws GLib.Error {
+        music.artist = this.get_string (props, "Artist");
+        music.album = this.get_string (props, "Album");
+        music.genre = this.get_string (props, "Genre");
+
+        var value = props.lookup ("AlbumArt");
         if (value != null) {
             var cover_factory = new AlbumArtFactory ();
-            var album_art = yield cover_factory.create ((string) value,
-                                                        service_name,
-                                                        host_ip);
-            item.thumbnails.add (album_art);
+
+            music.album_art = yield cover_factory.create ((string) value,
+                                                          service_name,
+                                                          host_ip);
         }
 
-        // Properties specific to video and image
+        this.set_audio_metadata (music, props, service_name, host_ip);
+    }
 
-        item.width = this.get_int (props, "Width");
-        item.height = this.get_int (props, "Height");
-        item.color_depth = this.get_int (props, "ColorDepth");
-        item.pixel_width = this.get_int (props, "PixelWidth");
-        item.pixel_height = this.get_int (props, "PixelHeight");
+    private void set_audio_metadata (AudioItem                audio,
+                                     HashTable<string,Value?> props,
+                                     string                   service_name,
+                                     string                   host_ip)
+                                     throws GLib.Error {
+        audio.duration = this.get_int (props, "Duration");
+        audio.bitrate = this.get_int (props, "Bitrate");
+        audio.sample_freq = this.get_int (props, "SampleRate");
+        audio.bits_per_sample = this.get_int (props, "BitsPerSample");
+    }
 
-        value = props.lookup ("Thumbnail");
+    private async void set_visual_metadata (
+                                        VisualItem               visual,
+                                        HashTable<string,Value?> props,
+                                        string                   service_name,
+                                        string                   host_ip)
+                                        throws GLib.Error {
+        visual.width = this.get_int (props, "Width");
+        visual.height = this.get_int (props, "Height");
+        visual.color_depth = this.get_int (props, "ColorDepth");
+        visual.pixel_width = this.get_int (props, "PixelWidth");
+        visual.pixel_height = this.get_int (props, "PixelHeight");
+
+        var value = props.lookup ("Thumbnail");
         if (value != null) {
             var factory = new ThumbnailFactory ();
             var thumbnail = yield factory.create ((string) value,
                                                   service_name,
                                                   host_ip);
-            item.thumbnails.add (thumbnail);
+            visual.thumbnails.add (thumbnail);
         }
+    }
 
-        return item;
+    private async void set_video_metadata (
+                                        VideoItem                video,
+                                        HashTable<string,Value?> props,
+                                        string                   service_name,
+                                        string                   host_ip)
+                                        throws GLib.Error {
+        yield this.set_visual_metadata (video, props, service_name, host_ip);
+        this.set_audio_metadata (video, props, service_name, host_ip);
     }
 
     private string? get_string (HashTable<string,Value?> props, string prop) {
diff --git a/src/plugins/gst-launch/Makefile.am b/src/plugins/gst-launch/Makefile.am
index 575d925..06312b5 100644
--- a/src/plugins/gst-launch/Makefile.am
+++ b/src/plugins/gst-launch/Makefile.am
@@ -14,7 +14,9 @@ AM_CFLAGS = $(LIBGUPNP_CFLAGS) \
 
 librygel_gst_launch_la_SOURCES = rygel-gst-launch-plugin.vala \
 				rygel-gst-launch-root-container.vala \
-				rygel-gst-launch-item.vala
+				rygel-gst-launch-item.vala \
+				rygel-gst-launch-audio-item.vala \
+				rygel-gst-launch-video-item.vala
 
 librygel_gst_launch_la_VALAFLAGS = --vapidir=$(top_srcdir)/src/rygel \
 			     --pkg rygel-1.0 --pkg gconf-2.0 \
diff --git a/src/plugins/test/rygel-test-item.vala b/src/plugins/gst-launch/rygel-gst-launch-audio-item.vala
similarity index 54%
copy from src/plugins/test/rygel-test-item.vala
copy to src/plugins/gst-launch/rygel-gst-launch-audio-item.vala
index f4bb394..d68fb6f 100644
--- a/src/plugins/test/rygel-test-item.vala
+++ b/src/plugins/gst-launch/rygel-gst-launch-audio-item.vala
@@ -1,7 +1,8 @@
 /*
- * Copyright (C) 2008 Zeeshan Ali (Khattak) <zeeshanak gnome org>.
- * Copyright (C) 2008 Nokia Corporation.
+ * Copyright (C) 2009 Thijs Vermeir <thijsvermeir gmail com>
+ * Copyright (C) 2010 Nokia Corporation.
  *
+ * Author: Thijs Vermeir <thijsvermeir gmail com>
  * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
  *                               <zeeshan ali nokia com>
  *
@@ -22,24 +23,26 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-using GUPnP;
 using Gst;
 
 /**
- * Represents Test item.
+ * Audio item that serves data from a gst-launch commandline.
  */
-public abstract class Rygel.Test.Item : Rygel.MediaItem {
-    const string TEST_AUTHOR = "Zeeshan Ali (Khattak)";
+public class Rygel.GstLaunch.AudioItem : Rygel.AudioItem, Item {
+    public string launch_line { get; protected set; }
 
-    public Item (string         id,
-                 MediaContainer parent,
-                 string         title,
-                 string         mime,
-                 string         upnp_class) {
-        base (id, parent, title, upnp_class);
+    public AudioItem (string         id,
+                      MediaContainer parent,
+                      string         title,
+                      string         mime_type,
+                      string         launch_line) {
+        base (id, parent, title);
 
-        this.mime_type = mime;
-        this.author = TEST_AUTHOR;
+        this.mime_type = mime_type;
+        this.launch_line = launch_line;
     }
-}
 
+    public override Element? create_stream_source () {
+        return this.create_source ();
+    }
+}
diff --git a/src/plugins/gst-launch/rygel-gst-launch-item.vala b/src/plugins/gst-launch/rygel-gst-launch-item.vala
index 29c4306..c00dd9f 100644
--- a/src/plugins/gst-launch/rygel-gst-launch-item.vala
+++ b/src/plugins/gst-launch/rygel-gst-launch-item.vala
@@ -1,7 +1,10 @@
 /*
  * Copyright (C) 2009 Thijs Vermeir <thijsvermeir gmail com>
+ * Copyright (C) 2010 Nokia Corporation.
  *
  * Author: Thijs Vermeir <thijsvermeir gmail com>
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *                               <zeeshan ali nokia com>
  *
  * This file is part of Rygel.
  *
@@ -23,31 +26,12 @@
 using Gst;
 
 /**
- * Represents Test audio item.
+ * Item that serves data from a gst-launch commandline.
  */
-public class Rygel.GstLaunch.Item : Rygel.MediaItem {
-    string launch_line;
+public interface Rygel.GstLaunch.Item : Rygel.MediaItem {
+    public abstract string launch_line { get; protected set; }
 
-    public Item (string         id,
-                 MediaContainer parent,
-                 string         title,
-                 string         mime_type,
-                 string         launch_line) {
-        string upnp_class;
-
-        if (mime_type.has_prefix ("audio")) {
-             upnp_class = MediaItem.AUDIO_CLASS;
-        } else {
-             upnp_class = MediaItem.VIDEO_CLASS;
-        }
-
-        base (id, parent, title, upnp_class);
-
-        this.mime_type = mime_type;
-        this.launch_line = launch_line;
-    }
-
-    public override Element? create_stream_source () {
+    protected Element? create_source () {
         try {
           return Gst.parse_bin_from_description (this.launch_line, true);
         } catch (Error err) {
diff --git a/src/plugins/gst-launch/rygel-gst-launch-root-container.vala b/src/plugins/gst-launch/rygel-gst-launch-root-container.vala
index 59643a7..a433cf1 100644
--- a/src/plugins/gst-launch/rygel-gst-launch-root-container.vala
+++ b/src/plugins/gst-launch/rygel-gst-launch-root-container.vala
@@ -58,11 +58,19 @@ public class Rygel.GstLaunch.RootContainer : SimpleContainer {
             var launch_line = config.get_string (CONFIG_GROUP,
                                                  "%s-launch".printf (name));
 
-            this.add_child (new Item (name,
-                                      this,
-                                      title,
-                                      mime_type,
-                                      launch_line));
+            if (mime_type.has_prefix ("audio")) {
+                this.add_child (new AudioItem (name,
+                                               this,
+                                               title,
+                                               mime_type,
+                                               launch_line));
+            } else {
+                this.add_child (new VideoItem (name,
+                                               this,
+                                               title,
+                                               mime_type,
+                                               launch_line));
+            }
         } catch (GLib.Error err) {
             debug ("GstLaunch failed item '%s': %s", name, err.message);
         }
diff --git a/src/plugins/test/rygel-test-item.vala b/src/plugins/gst-launch/rygel-gst-launch-video-item.vala
similarity index 54%
rename from src/plugins/test/rygel-test-item.vala
rename to src/plugins/gst-launch/rygel-gst-launch-video-item.vala
index f4bb394..3cfa0bc 100644
--- a/src/plugins/test/rygel-test-item.vala
+++ b/src/plugins/gst-launch/rygel-gst-launch-video-item.vala
@@ -1,7 +1,8 @@
 /*
- * Copyright (C) 2008 Zeeshan Ali (Khattak) <zeeshanak gnome org>.
- * Copyright (C) 2008 Nokia Corporation.
+ * Copyright (C) 2009 Thijs Vermeir <thijsvermeir gmail com>
+ * Copyright (C) 2010 Nokia Corporation.
  *
+ * Author: Thijs Vermeir <thijsvermeir gmail com>
  * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
  *                               <zeeshan ali nokia com>
  *
@@ -22,24 +23,26 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
-using GUPnP;
 using Gst;
 
 /**
- * Represents Test item.
+ * Video item that serves data from a gst-launch commandline.
  */
-public abstract class Rygel.Test.Item : Rygel.MediaItem {
-    const string TEST_AUTHOR = "Zeeshan Ali (Khattak)";
+public class Rygel.GstLaunch.VideoItem : Rygel.VideoItem, Item {
+    public string launch_line { get; protected set; }
 
-    public Item (string         id,
-                 MediaContainer parent,
-                 string         title,
-                 string         mime,
-                 string         upnp_class) {
-        base (id, parent, title, upnp_class);
+    public VideoItem (string         id,
+                      MediaContainer parent,
+                      string         title,
+                      string         mime_type,
+                      string         launch_line) {
+        base (id, parent, title);
 
-        this.mime_type = mime;
-        this.author = TEST_AUTHOR;
+        this.mime_type = mime_type;
+        this.launch_line = launch_line;
     }
-}
 
+    public override Element? create_stream_source () {
+        return this.create_source ();
+    }
+}
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 f133c69..fa9bb33 100644
--- a/src/plugins/media-export/rygel-media-export-harvesting-task.vala
+++ b/src/plugins/media-export/rygel-media-export-harvesting-task.vala
@@ -287,13 +287,13 @@ public class Rygel.MediaExport.HarvestingTask : Rygel.StateMachine, GLib.Object
 
         MediaItem item;
         if (dlna == null) {
-            item = new Item.simple (this.current_parent (),
+            item = ItemFactory.create_simple (this.current_parent (),
                                     file,
                                     mime,
                                     size,
                                     mtime);
         } else {
-            item = Item.create_from_info (this.current_parent (),
+            item = ItemFactory.create_from_info (this.current_parent (),
                                           file,
                                           dlna,
                                           mime,
diff --git a/src/plugins/media-export/rygel-media-export-item.vala b/src/plugins/media-export/rygel-media-export-item.vala
index 5e1945f..63d1992 100644
--- a/src/plugins/media-export/rygel-media-export-item.vala
+++ b/src/plugins/media-export/rygel-media-export-item.vala
@@ -27,36 +27,38 @@ using Gst;
 /**
  * Represents MediaExport item.
  */
-public class Rygel.MediaExport.Item : Rygel.MediaItem {
-    public Item.simple (MediaContainer parent,
-                        File           file,
-                        string         mime,
-                        uint64         size,
-                        uint64         mtime) {
+namespace Rygel.MediaExport.ItemFactory {
+    public static MediaItem create_simple (MediaContainer parent,
+                                           File           file,
+                                           string         mime,
+                                           uint64         size,
+                                           uint64         mtime) {
         var title = file.get_basename ();
-        string upnp_class;
+        MediaItem item;
 
         if (mime.has_prefix ("video/")) {
-            upnp_class = MediaItem.VIDEO_CLASS;
+            item = new VideoItem (MediaCache.get_id (file), parent, title);
         } else if (mime.has_prefix ("image/")) {
-            upnp_class = MediaItem.PHOTO_CLASS;
+            item = new PhotoItem (MediaCache.get_id (file), parent, title);
         } else {
-            upnp_class = MediaItem.AUDIO_CLASS;
+            item = new MusicItem (MediaCache.get_id (file), parent, title);
         }
 
-        base (MediaCache.get_id (file), parent, title, upnp_class);
-        this.mime_type = mime;
-        this.size = (int64) size;
-        this.modified = mtime;
-        this.add_uri (file.get_uri ());
+        item.mime_type = mime;
+        item.size = (int64) size;
+        item.modified = mtime;
+        item.add_uri (file.get_uri ());
+
+        return item;
     }
 
-    public static Item? create_from_info (MediaContainer        parent,
-                                          File                  file,
-                                          GUPnP.DLNAInformation dlna_info,
-                                          string                mime,
-                                          uint64                size,
-                                          uint64                mtime) {
+    public static MediaItem? create_from_info (MediaContainer        parent,
+                                               File                  file,
+                                               GUPnP.DLNAInformation dlna_info,
+                                               string                mime,
+                                               uint64                size,
+                                               uint64                mtime) {
+        MediaItem item;
         string id = MediaCache.get_id (file);
         unowned StreamAudioInformation audio_info = null;
         unowned StreamVideoInformation video_info = null;
@@ -76,154 +78,171 @@ public class Rygel.MediaExport.Item : Rygel.MediaItem {
         if (video_info != null) {
             if (audio_info == null &&
                 video_info.streamtype == Gst.StreamType.IMAGE) {
-                return new Item.photo (parent,
-                                       id,
-                                       file,
-                                       dlna_info,
-                                       video_info,
-                                       mime,
-                                       size,
-                                       mtime);
+                item = new PhotoItem (id, parent, "");
+                return fill_photo_item (item as PhotoItem,
+                                        file,
+                                        dlna_info,
+                                        video_info,
+                                        mime,
+                                        size,
+                                        mtime);
             } else {
-                return new Item.video (parent,
-                                       id,
-                                       file,
-                                       dlna_info,
-                                       video_info,
-                                       audio_info,
-                                       mime,
-                                       size,
-                                       mtime);
+                item = new VideoItem (id, parent, "");
+                return fill_video_item (item as VideoItem,
+                                      file,
+                                      dlna_info,
+                                      video_info,
+                                      audio_info,
+                                      mime,
+                                      size,
+                                      mtime);
             }
         } else if (audio_info != null) {
-            return new Item.audio (parent,
-                                   id,
-                                   file,
-                                   dlna_info,
-                                   audio_info,
-                                   mime,
-                                   size,
-                                   mtime);
+            item = new MusicItem (id, parent, "");
+            return fill_music_item (item as MusicItem,
+                                    file,
+                                    dlna_info,
+                                    audio_info,
+                                    mime,
+                                    size,
+                                    mtime);
         } else {
             return null;
         }
     }
 
-    private Item.video (MediaContainer              parent,
-                        string                      id,
-                        File                        file,
-                        GUPnP.DLNAInformation       dlna_info,
-                        Gst.StreamVideoInformation  video_info,
-                        Gst.StreamAudioInformation? audio_info,
-                        string                      mime,
-                        uint64                      size,
-                        uint64                      mtime) {
-        this (parent,
-              id,
-              file,
-              dlna_info,
-              mime,
-              size,
-              mtime,
-              MediaItem.VIDEO_CLASS);
-
-        this.width = (int) video_info.width;
-        this.height = (int) video_info.height;
-        this.color_depth = (int) video_info.depth;
+    private static void fill_audio_item (AudioItem               item,
+                                         DLNAInformation         dlna_info,
+                                         StreamAudioInformation? audio_info) {
+        if (dlna_info.info.duration > 0) {
+            item.duration = dlna_info.info.duration / Gst.SECOND;
+        } else {
+            item.duration = -1;
+        }
+
+
+         if (audio_info != null) {
+            if (audio_info.tags != null) {
+                 uint tmp;
+                audio_info.tags.get_uint (TAG_BITRATE, out tmp);
+                item.bitrate = (int) tmp / 8;
+            }
+            item.n_audio_channels = (int) audio_info.channels;
+            item.sample_freq = (int) audio_info.sample_rate;
+        }
+    }
+
+
+    private static MediaItem fill_video_item (VideoItem               item,
+                                       File                    file,
+                                       DLNAInformation         dlna_info,
+                                       StreamVideoInformation  video_info,
+                                       StreamAudioInformation? audio_info,
+                                       string                  mime,
+                                       uint64                  size,
+                                       uint64                  mtime) {
+        fill_audio_item (item as AudioItem, dlna_info, audio_info);
+        fill_media_item (item, file, dlna_info, mime, size, mtime);
+
+        item.width = (int) video_info.width;
+        item.height = (int) video_info.height;
+        item.color_depth = (int) video_info.depth;
 
         if (audio_info != null) {
-            this.n_audio_channels = (int) audio_info.channels;
-            this.sample_freq = (int) audio_info.sample_rate;
+            item.n_audio_channels = (int) audio_info.channels;
+            item.sample_freq = (int) audio_info.sample_rate;
             if (audio_info.tags != null) {
                 uint tmp;
 
                 audio_info.tags.get_uint (TAG_BITRATE, out tmp);
-                this.bitrate = (int) tmp / 8;
+                item.bitrate = (int) tmp / 8;
             }
         }
+
+        return item;
     }
 
-    private Item.photo (MediaContainer             parent,
-                        string                     id,
-                        File                       file,
-                        GUPnP.DLNAInformation      dlna_info,
-                        Gst.StreamVideoInformation video_info,
-                        string                     mime,
-                        uint64                     size,
-                        uint64                     mtime) {
-        this (parent,
-              id,
-              file,
-              dlna_info,
-              mime,
-              size,
-              mtime,
-              MediaItem.PHOTO_CLASS);
-
-        this.width = (int) video_info.width;
-        this.height = (int) video_info.height;
-        this.color_depth = (int) video_info.depth;
+    private static MediaItem fill_photo_item (PhotoItem              item,
+                                       File                   file,
+                                       DLNAInformation        dlna_info,
+                                       StreamVideoInformation video_info,
+                                       string                 mime,
+                                       uint64                 size,
+                                       uint64                 mtime) {
+        fill_media_item (item,
+                         file,
+                         dlna_info,
+                         mime,
+                         size,
+                         mtime);
+
+        item.width = (int) video_info.width;
+        item.height = (int) video_info.height;
+        item.color_depth = (int) video_info.depth;
+
+        return item;
     }
 
-    private Item.audio (MediaContainer             parent,
-                        string                     id,
-                        File                       file,
-                        GUPnP.DLNAInformation      dlna_info,
-                        Gst.StreamAudioInformation audio_info,
-                        string                     mime,
-                        uint64                     size,
-                        uint64                     mtime) {
-        this (parent,
-              id,
-              file,
-              dlna_info,
-              mime,
-              size,
-              mtime,
-              MediaItem.MUSIC_CLASS);
-
-        if (audio_info.tags != null) {
-            unowned Gst.Buffer buffer;
-            audio_info.tags.get_buffer (TAG_IMAGE, out buffer);
-            if (buffer != null) {
-                var structure = buffer.caps.get_structure (0);
-                int image_type;
-                structure.get_enum ("image-type",
-                                    typeof (Gst.TagImageType),
-                                    out image_type);
-                switch (image_type) {
-                    case TagImageType.UNDEFINED:
-                    case TagImageType.FRONT_COVER:
-                        var store = MediaArtStore.get_default ();
-                        var thumb = store.get_media_art_file ("album",
-                                                              this,
-                                                              true);
-                        try {
-                            var writer = new JPEGWriter ();
-                            writer.write (buffer, thumb);
-                        } catch (Error error) {}
-                        break;
-                    default:
-                        break;
+    private static MediaItem fill_music_item (MusicItem              item,
+                                       File                   file,
+                                       DLNAInformation        dlna_info,
+                                       StreamAudioInformation? audio_info,
+                                       string                 mime,
+                                       uint64                 size,
+                                       uint64                 mtime) {
+        fill_audio_item (item as AudioItem, dlna_info, audio_info);
+        fill_media_item (item,
+                         file,
+                         dlna_info,
+                         mime,
+                         size,
+                         mtime);
+
+        if (audio_info != null) {
+            if (audio_info.tags != null) {
+                unowned Gst.Buffer buffer;
+                audio_info.tags.get_buffer (TAG_IMAGE, out buffer);
+                if (buffer != null) {
+                    var structure = buffer.caps.get_structure (0);
+                    int image_type;
+                    structure.get_enum ("image-type",
+                            typeof (Gst.TagImageType),
+                            out image_type);
+                    switch (image_type) {
+                        case TagImageType.UNDEFINED:
+                        case TagImageType.FRONT_COVER:
+                            var store = MediaArtStore.get_default ();
+                            var thumb = store.get_media_art_file ("album",
+                                    item,
+                                    true);
+                            try {
+                                var writer = new JPEGWriter ();
+                                writer.write (buffer, thumb);
+                            } catch (Error error) {}
+                            break;
+                        default:
+                            break;
+                    }
                 }
             }
+            dlna_info.info.tags.get_string (TAG_ARTIST, out item.artist);
+            dlna_info.info.tags.get_string (TAG_ALBUM, out item.album);
+            dlna_info.info.tags.get_string (TAG_GENRE, out item.genre);
 
             uint tmp;
-            audio_info.tags.get_uint (TAG_BITRATE, out tmp);
-            this.bitrate = (int) tmp / 8;
+            dlna_info.info.tags.get_uint (TAG_TRACK_NUMBER, out tmp);
+            item.track_number = (int) tmp;
         }
-        this.n_audio_channels = (int) audio_info.channels;
-        this.sample_freq = (int) audio_info.sample_rate;
+
+        return item;
     }
 
-    private Item (MediaContainer        parent,
-                  string                id,
-                  File                  file,
-                  GUPnP.DLNAInformation dlna_info,
-                  string                mime,
-                  uint64                size,
-                  uint64                mtime,
-                  string                upnp_class) {
+    private static void fill_media_item (MediaItem       item,
+                                       File                   file,
+                                  DLNAInformation dlna_info,
+                                  string           mime,
+                                  uint64           size,
+                                  uint64           mtime) {
         string title = null;
 
         if (dlna_info.info.tags == null ||
@@ -231,48 +250,34 @@ public class Rygel.MediaExport.Item : Rygel.MediaItem {
             title = file.get_basename ();
         }
 
-        base (id, parent, title, upnp_class);
-
-        if (dlna_info.info.duration > 0) {
-            this.duration = dlna_info.info.duration / Gst.SECOND;
-        } else {
-            this.duration = -1;
-        }
+        item.title = title;
 
         if (dlna_info.info.tags != null) {
-            dlna_info.info.tags.get_string (TAG_ARTIST, out this.author);
-            dlna_info.info.tags.get_string (TAG_ALBUM, out this.album);
-            dlna_info.info.tags.get_string (TAG_GENRE, out this.genre);
-
-            uint tmp;
-            dlna_info.info.tags.get_uint (TAG_TRACK_NUMBER, out tmp);
-            this.track_number = (int) tmp;
-
             GLib.Date? date;
             if (dlna_info.info.tags.get_date (TAG_DATE, out date)) {
                 char[] datestr = new char[30];
                 date.strftime (datestr, "%F");
-                this.date = (string) datestr;
+                item.date = (string) datestr;
             }
         }
 
         // use mtime if no time tag was available
-        if (this.date == null) {
+        if (item.date == null) {
             TimeVal tv = { (long) mtime, 0 };
-            this.date = tv.to_iso8601 ();
+            item.date = tv.to_iso8601 ();
         }
 
-        this.size = (int64) size;
-        this.modified = (int64) mtime;
+        item.size = (int64) size;
+        item.modified = (int64) mtime;
 
         if (dlna_info.name != null) {
-            this.dlna_profile = dlna_info.name;
-            this.mime_type = dlna_info.mime;
+            item.dlna_profile = dlna_info.name;
+            item.mime_type = dlna_info.mime;
         } else {
-            this.mime_type = mime;
+            item.mime_type = mime;
         }
 
-        this.add_uri (file.get_uri ());
+        item.add_uri (file.get_uri ());
     }
 }
 
diff --git a/src/plugins/media-export/rygel-media-export-media-cache.vala b/src/plugins/media-export/rygel-media-export-media-cache.vala
index 701eced..0e38478 100644
--- a/src/plugins/media-export/rygel-media-export-media-cache.vala
+++ b/src/plugins/media-export/rygel-media-export-media-cache.vala
@@ -415,25 +415,57 @@ public class Rygel.MediaExport.MediaCache : Object {
         }
     }
 
+
+
     private void save_metadata (Rygel.MediaItem item) throws Error {
+        // Fill common properties
         GLib.Value[] values = { item.size,
                                 item.mime_type,
-                                item.width,
-                                item.height,
+                                0,
+                                0,
                                 item.upnp_class,
-                                item.author,
-                                item.album,
+                                0,
+                                0,
                                 item.date,
-                                item.bitrate,
-                                item.sample_freq,
-                                item.bits_per_sample,
-                                item.n_audio_channels,
-                                item.track_number,
-                                item.color_depth,
-                                item.duration,
+                                0,
+                                0,
+                                0,
+                                0,
+                                0,
+                                0,
+                                0,
                                 item.id,
                                 item.dlna_profile,
-                                item.genre};
+                                0};
+
+        if (item is AudioItem) {
+            var audio_item = item as AudioItem;
+            values[14] = audio_item.duration;
+            values[8] = audio_item.bitrate;
+            values[9] = audio_item.sample_freq;
+            values[10] = audio_item.bits_per_sample;
+            values[11] = audio_item.n_audio_channels;
+            if (item is MusicItem) {
+                var music_item = item as MusicItem;
+                values[5] = music_item.artist;
+                values[6] = music_item.album;
+                values[17] = music_item.genre;
+                values[12] = music_item.track_number;
+                music_item.lookup_album_art ();
+            }
+        }
+
+        if (item is VisualItem) {
+            var visual_item = item as VisualItem;
+            values[2] = visual_item.width;
+            values[3] = visual_item.height;
+            values[13] = visual_item.color_depth;
+            if (item is VideoItem) {
+                var video_item = item as VideoItem;
+                values[5] = video_item.author;
+            }
+        }
+
         this.db.exec (this.sql.make (SQLString.SAVE_METADATA), values);
     }
 
@@ -519,10 +551,6 @@ public class Rygel.MediaExport.MediaCache : Object {
                                            upnp_class);
                 fill_item (statement, object as MediaItem);
 
-                if (upnp_class.has_prefix (MediaItem.AUDIO_CLASS)) {
-                    (object as MediaItem).lookup_album_art ();
-                }
-
                 var uri = statement.column_text (DetailColumn.URI);
                 if (uri != null) {
                     (object as MediaItem).add_uri (uri);
@@ -540,27 +568,41 @@ public class Rygel.MediaExport.MediaCache : Object {
     }
 
     private void fill_item (Statement statement, MediaItem item) {
-        item.author = statement.column_text (DetailColumn.AUTHOR);
-        item.album = statement.column_text (DetailColumn.ALBUM);
+        // Fill common properties
         item.date = statement.column_text (DetailColumn.DATE);
         item.mime_type = statement.column_text (DetailColumn.MIME_TYPE);
-        item.duration = (long) statement.column_int64 (DetailColumn.DURATION);
-
+        item.dlna_profile = statement.column_text (DetailColumn.DLNA_PROFILE);
         item.size = statement.column_int64 (DetailColumn.SIZE);
-        item.bitrate = statement.column_int (DetailColumn.BITRATE);
 
-        item.sample_freq = statement.column_int (DetailColumn.SAMPLE_FREQ);
-        item.bits_per_sample = statement.column_int (
+        if (item is AudioItem) {
+            var audio_item = item as AudioItem;
+            audio_item.duration = (long) statement.column_int64 (DetailColumn.DURATION);
+            audio_item.bitrate = statement.column_int (DetailColumn.BITRATE);
+            audio_item.sample_freq = statement.column_int (DetailColumn.SAMPLE_FREQ);
+            audio_item.bits_per_sample = statement.column_int (
                                         DetailColumn.BITS_PER_SAMPLE);
-        item.n_audio_channels = statement.column_int (
+            audio_item.n_audio_channels = statement.column_int (
                                         DetailColumn.CHANNELS);
-        item.track_number = statement.column_int (DetailColumn.TRACK);
+            if (item is MusicItem) {
+                var music_item = item as MusicItem;
+                music_item.artist = statement.column_text (DetailColumn.AUTHOR);
+                music_item.album = statement.column_text (DetailColumn.ALBUM);
+                music_item.genre = statement.column_text (DetailColumn.GENRE);
+                music_item.track_number = statement.column_int (DetailColumn.TRACK);
+                music_item.lookup_album_art ();
+            }
+        }
 
-        item.width = statement.column_int (DetailColumn.WIDTH);
-        item.height = statement.column_int (DetailColumn.HEIGHT);
-        item.color_depth = statement.column_int (DetailColumn.COLOR_DEPTH);
-        item.dlna_profile = statement.column_text (DetailColumn.DLNA_PROFILE);
-        item.genre = statement.column_text (DetailColumn.GENRE);
+        if (item is VisualItem) {
+            var visual_item = item as VisualItem;
+            visual_item.width = statement.column_int (DetailColumn.WIDTH);
+            visual_item.height = statement.column_int (DetailColumn.HEIGHT);
+            visual_item.color_depth = statement.column_int (DetailColumn.COLOR_DEPTH);
+            if (item is VideoItem) {
+                var video_item = item as VideoItem;
+                video_item.author = statement.column_text (DetailColumn.AUTHOR);
+            }
+        }
     }
 
     public ArrayList<string> get_child_ids (string container_id)
diff --git a/src/plugins/media-export/rygel-media-export-object-factory.vala b/src/plugins/media-export/rygel-media-export-object-factory.vala
index ea90b28..88fb43d 100644
--- a/src/plugins/media-export/rygel-media-export-object-factory.vala
+++ b/src/plugins/media-export/rygel-media-export-object-factory.vala
@@ -53,6 +53,15 @@ internal class Rygel.MediaExport.ObjectFactory : Object {
                                        string         id,
                                        string         title,
                                        string         upnp_class) {
-        return new MediaItem (id, parent, title, upnp_class);
+        switch (upnp_class) {
+            case MusicItem.UPNP_CLASS:
+                return new MusicItem (id, parent, title);
+            case VideoItem.UPNP_CLASS:
+                return new VideoItem (id, parent, title);
+            case PhotoItem.UPNP_CLASS:
+                return new PhotoItem (id, parent, title);
+            default:
+                assert_not_reached ();
+        }
     }
 }
diff --git a/src/plugins/mediathek/rygel-mediathek-video-item.vala b/src/plugins/mediathek/rygel-mediathek-video-item.vala
index 473d480..4deb7bf 100644
--- a/src/plugins/mediathek/rygel-mediathek-video-item.vala
+++ b/src/plugins/mediathek/rygel-mediathek-video-item.vala
@@ -27,12 +27,11 @@ public errordomain Rygel.Mediathek.VideoItemError {
     XML_PARSE_ERROR
 }
 
-public class Rygel.Mediathek.VideoItem : Rygel.MediaItem {
+public class Rygel.Mediathek.VideoItem : Rygel.VideoItem {
     private VideoItem (MediaContainer parent, string title) {
-        base(Checksum.compute_for_string (ChecksumType.MD5, title), 
-             parent, 
-             title, 
-             MediaItem.VIDEO_CLASS);
+        base (Checksum.compute_for_string (ChecksumType.MD5, title),
+              parent,
+              title);
 
         this.mime_type = "video/x-ms-wmv";
         this.author = "ZDF - Second German TV Channel Streams";
diff --git a/src/plugins/test/Makefile.am b/src/plugins/test/Makefile.am
index 77e6fae..581d772 100644
--- a/src/plugins/test/Makefile.am
+++ b/src/plugins/test/Makefile.am
@@ -18,7 +18,6 @@ AM_CFLAGS = $(LIBGUPNP_CFLAGS) \
 	    -include config.h
 
 librygel_test_la_SOURCES = rygel-test-root-container.vala \
-			   rygel-test-item.vala \
 			   rygel-test-audio-item.vala \
 			   rygel-test-video-item.vala \
 			   rygel-test-plugin.vala
diff --git a/src/plugins/test/rygel-test-audio-item.vala b/src/plugins/test/rygel-test-audio-item.vala
index 69bba43..36b1b39 100644
--- a/src/plugins/test/rygel-test-audio-item.vala
+++ b/src/plugins/test/rygel-test-audio-item.vala
@@ -27,16 +27,14 @@ using Gst;
 /**
  * Represents Test audio item.
  */
-public class Rygel.Test.AudioItem : Item {
+public class Rygel.Test.AudioItem : Rygel.AudioItem {
     private const string TEST_MIMETYPE = "audio/x-wav";
     private const string PIPELINE = "audiotestsrc is-live=1 ! wavenc";
 
     public AudioItem (string id, MediaContainer parent, string title) {
-        base (id,
-              parent,
-              title,
-              TEST_MIMETYPE,
-              MediaItem.AUDIO_CLASS);
+        base (id, parent, title);
+
+        this.mime_type = TEST_MIMETYPE;
     }
 
     public override Element? create_stream_source () {
diff --git a/src/plugins/test/rygel-test-video-item.vala b/src/plugins/test/rygel-test-video-item.vala
index c43498c..876d271 100644
--- a/src/plugins/test/rygel-test-video-item.vala
+++ b/src/plugins/test/rygel-test-video-item.vala
@@ -27,18 +27,16 @@ using Gst;
 /**
  * Represents Test video item.
  */
-public class Rygel.Test.VideoItem : Item {
+public class Rygel.Test.VideoItem : Rygel.VideoItem {
     private const string TEST_MIMETYPE = "video/mpeg";
     private const string PIPELINE = "videotestsrc is-live=1 ! " +
                                     "ffenc_mpeg2video ! " +
                                     "mpegtsmux";
 
     public VideoItem (string id, MediaContainer parent, string title) {
-        base (id,
-              parent,
-              title,
-              TEST_MIMETYPE,
-              MediaItem.VIDEO_CLASS);
+        base (id, parent, title);
+
+        this.mime_type = TEST_MIMETYPE;
     }
 
     public override Element? create_stream_source () {
diff --git a/src/plugins/tracker/rygel-tracker-item-factory.vala b/src/plugins/tracker/rygel-tracker-item-factory.vala
index cc01ae2..f9aa48e 100644
--- a/src/plugins/tracker/rygel-tracker-item-factory.vala
+++ b/src/plugins/tracker/rygel-tracker-item-factory.vala
@@ -68,13 +68,15 @@ public abstract class Rygel.Tracker.ItemFactory {
         this.key_chains[Metadata.DATE].add ("nie:contentCreated");
     }
 
-    public virtual MediaItem create (string          id,
-                                     string          uri,
-                                     SearchContainer parent,
-                                     string[]        metadata)
-                                     throws GLib.Error {
-        var item = new MediaItem (id, parent, "", this.upnp_class);
-
+    public abstract MediaItem create (string          id,
+                                      string          uri,
+                                      SearchContainer parent,
+                                      string[]        metadata)
+                                      throws GLib.Error;
+
+    protected virtual void set_metadata (MediaItem item,
+                                         string    uri,
+                                         string[]  metadata) throws GLib.Error {
         if (metadata[Metadata.TITLE] != "")
             item.title = metadata[Metadata.TITLE];
         else
@@ -93,8 +95,6 @@ public abstract class Rygel.Tracker.ItemFactory {
         item.mime_type = metadata[Metadata.MIME];
 
         item.add_uri (uri);
-
-        return item;
     }
 }
 
diff --git a/src/plugins/tracker/rygel-tracker-music-item-factory.vala b/src/plugins/tracker/rygel-tracker-music-item-factory.vala
index c8dee6c..899fcf0 100644
--- a/src/plugins/tracker/rygel-tracker-music-item-factory.vala
+++ b/src/plugins/tracker/rygel-tracker-music-item-factory.vala
@@ -41,7 +41,7 @@ public class Rygel.Tracker.MusicItemFactory : ItemFactory {
 
     public MusicItemFactory () {
         base (CATEGORY,
-              MediaItem.MUSIC_CLASS,
+              MusicItem.UPNP_CLASS,
               MUSIC_RESOURCES_CLASS_PATH,
               Environment.get_user_special_dir (UserDirectory.MUSIC));
 
@@ -61,27 +61,38 @@ public class Rygel.Tracker.MusicItemFactory : ItemFactory {
     public override MediaItem create (string          id,
                                       string          uri,
                                       SearchContainer parent,
-                                      string[]               metadata)
+                                      string[]        metadata)
                                       throws GLib.Error {
-        var item = base.create (id, uri, parent, metadata);
+        var item = new MusicItem (id, parent, "");
+
+        this.set_metadata (item, uri, metadata);
+
+        return item;
+    }
+
+    protected override void set_metadata (MediaItem item,
+                                          string    uri,
+                                          string[]  metadata)
+                                          throws GLib.Error {
+        base.set_metadata (item, uri, metadata);
+
+        var music = item as MusicItem;
 
         if (metadata[MusicMetadata.DURATION] != "" &&
             metadata[MusicMetadata.DURATION] != "0") {
-            item.duration = metadata[MusicMetadata.DURATION].to_int ();
+            music.duration = metadata[MusicMetadata.DURATION].to_int ();
         }
 
         if (metadata[MusicMetadata.AUDIO_TRACK_NUM] != "") {
             var track_number = metadata[MusicMetadata.AUDIO_TRACK_NUM];
-            item.track_number = track_number.to_int ();
+            music.track_number = track_number.to_int ();
         }
 
-        item.author = metadata[MusicMetadata.AUDIO_ARTIST];
-        item.album = metadata[MusicMetadata.AUDIO_ALBUM];
-        item.genre = metadata[MusicMetadata.AUDIO_GENRE];
+        music.artist = metadata[MusicMetadata.AUDIO_ARTIST];
+        music.album = metadata[MusicMetadata.AUDIO_ALBUM];
+        music.genre = metadata[MusicMetadata.AUDIO_GENRE];
 
-        item.lookup_album_art ();
-
-        return item;
+        music.lookup_album_art ();
     }
 }
 
diff --git a/src/plugins/tracker/rygel-tracker-picture-item-factory.vala b/src/plugins/tracker/rygel-tracker-picture-item-factory.vala
index c8b162f..d722ed3 100644
--- a/src/plugins/tracker/rygel-tracker-picture-item-factory.vala
+++ b/src/plugins/tracker/rygel-tracker-picture-item-factory.vala
@@ -38,7 +38,7 @@ public class Rygel.Tracker.PictureItemFactory : ItemFactory {
 
     public PictureItemFactory () {
         base (CATEGORY,
-              MediaItem.PHOTO_CLASS,
+              PhotoItem.UPNP_CLASS,
               PHOTO_RESOURCES_CLASS_PATH,
               Environment.get_user_special_dir (UserDirectory.PICTURES));
 
@@ -55,15 +55,28 @@ public class Rygel.Tracker.PictureItemFactory : ItemFactory {
                                       SearchContainer parent,
                                       string[]        metadata)
                                       throws GLib.Error {
-        var item = base.create (id, uri, parent, metadata);
+        var item = new PhotoItem (id, parent, "");
 
-        if (metadata[PictureMetadata.WIDTH] != "")
-            item.width = metadata[PictureMetadata.WIDTH].to_int ();
-
-        if (metadata[PictureMetadata.HEIGHT] != "")
-            item.height = metadata[PictureMetadata.HEIGHT].to_int ();
+        this.set_metadata (item, uri, metadata);
 
         return item;
     }
+
+    protected override void set_metadata (MediaItem item,
+                                          string    uri,
+                                          string[]  metadata)
+                                          throws GLib.Error {
+        base.set_metadata (item, uri, metadata);
+
+        var photo = item as PhotoItem;
+
+        if (metadata[PictureMetadata.WIDTH] != "") {
+            photo.width = metadata[PictureMetadata.WIDTH].to_int ();
+        }
+
+        if (metadata[PictureMetadata.HEIGHT] != "") {
+            photo.height = metadata[PictureMetadata.HEIGHT].to_int ();
+        }
+    }
 }
 
diff --git a/src/plugins/tracker/rygel-tracker-video-item-factory.vala b/src/plugins/tracker/rygel-tracker-video-item-factory.vala
index 3e1d366..db66cdf 100644
--- a/src/plugins/tracker/rygel-tracker-video-item-factory.vala
+++ b/src/plugins/tracker/rygel-tracker-video-item-factory.vala
@@ -39,7 +39,7 @@ public class Rygel.Tracker.VideoItemFactory : ItemFactory {
 
     public VideoItemFactory () {
         base (CATEGORY,
-              MediaItem.VIDEO_CLASS,
+              VideoItem.UPNP_CLASS,
               VIDEO_RESOURCES_CLASS_PATH,
               Environment.get_user_special_dir (UserDirectory.VIDEOS));
 
@@ -57,18 +57,29 @@ public class Rygel.Tracker.VideoItemFactory : ItemFactory {
                                       SearchContainer parent,
                                       string[]        metadata)
                                       throws GLib.Error {
-        var item = base.create (id, uri, parent, metadata);
+        var item = new VideoItem (id, parent, "");
+
+        this.set_metadata (item, uri, metadata);
+
+        return item;
+    }
+
+    protected override void set_metadata (MediaItem item,
+                                          string    uri,
+                                          string[]  metadata)
+                                          throws GLib.Error {
+        base.set_metadata (item, uri, metadata);
+
+        var video = item as VideoItem;
 
         if (metadata[VideoMetadata.WIDTH] != "")
-            item.width = metadata[VideoMetadata.WIDTH].to_int ();
+            video.width = metadata[VideoMetadata.WIDTH].to_int ();
 
         if (metadata[VideoMetadata.HEIGHT] != "")
-            item.height = metadata[VideoMetadata.HEIGHT].to_int ();
+            video.height = metadata[VideoMetadata.HEIGHT].to_int ();
 
         if (metadata[VideoMetadata.DURATION] != "")
-            item.duration = metadata[VideoMetadata.DURATION].to_int ();
-
-        return item;
+            video.duration = metadata[VideoMetadata.DURATION].to_int ();
     }
 }
 
diff --git a/src/rygel/Makefile.am b/src/rygel/Makefile.am
index 6752c25..1c87ebc 100644
--- a/src/rygel/Makefile.am
+++ b/src/rygel/Makefile.am
@@ -82,6 +82,12 @@ VAPI_SOURCE_FILES = rygel-configuration.vala \
 		    rygel-media-container.vala \
 		    rygel-simple-container.vala \
 		    rygel-media-item.vala \
+		    rygel-audio-item.vala \
+		    rygel-music-item.vala \
+		    rygel-visual-item.vala \
+		    rygel-video-item.vala \
+		    rygel-image-item.vala \
+		    rygel-photo-item.vala \
 		    rygel-thumbnail.vala \
 		    rygel-thumbnailer.vala \
 		    rygel-album-art.vala \
diff --git a/src/rygel/rygel-audio-item.vala b/src/rygel/rygel-audio-item.vala
new file mode 100644
index 0000000..eb58f26
--- /dev/null
+++ b/src/rygel/rygel-audio-item.vala
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2010 Nokia Corporation.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *                               <zeeshan ali nokia com>
+ *
+ * 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 GUPnP;
+
+/**
+ * Represents an audio item.
+ */
+public class Rygel.AudioItem : MediaItem {
+    public new const string UPNP_CLASS = "object.item.audioItem";
+
+    public long duration = -1;  // Duration in seconds
+    public int bitrate = -1;    // Bytes/second
+
+    public int sample_freq = -1;
+    public int bits_per_sample = -1;
+    public int n_audio_channels = -1;
+
+    public AudioItem (string         id,
+                      MediaContainer parent,
+                      string         title,
+                      string         upnp_class = AudioItem.UPNP_CLASS) {
+        base (id, parent, title, upnp_class);
+    }
+
+    public override bool streamable () {
+        return true;
+    }
+
+    internal override DIDLLiteResource add_resource (
+                                        DIDLLiteItem didl_item,
+                                        string?      uri,
+                                        string       protocol,
+                                        string?      import_uri = null)
+                                        throws Error {
+        var res = base.add_resource (didl_item, uri, protocol, import_uri);
+
+        res.duration = this.duration;
+        res.bitrate = this.bitrate;
+        res.sample_freq = this.sample_freq;
+        res.bits_per_sample = this.bits_per_sample;
+        res.audio_channels = this.n_audio_channels;
+
+        return res;
+    }
+}
diff --git a/src/rygel/rygel-didl-lite-writer.vala b/src/rygel/rygel-didl-lite-writer.vala
index 9f92da9..cc17574 100644
--- a/src/rygel/rygel-didl-lite-writer.vala
+++ b/src/rygel/rygel-didl-lite-writer.vala
@@ -40,7 +40,7 @@ internal class Rygel.DIDLLiteWriter : GUPnP.DIDLLiteWriter {
 
     public void serialize (MediaObject media_object) throws Error {
         if (media_object is MediaItem) {
-            this.serialize_item ((MediaItem) media_object);
+            ((MediaItem) media_object).serialize (this);
         } else if (media_object is MediaContainer) {
             this.serialize_container ((MediaContainer) media_object);
         } else {
@@ -49,63 +49,6 @@ internal class Rygel.DIDLLiteWriter : GUPnP.DIDLLiteWriter {
         }
     }
 
-    private void serialize_item (MediaItem item) throws Error {
-        var didl_item = this.add_item ();
-
-        didl_item.id = item.id;
-        if (item.parent != null) {
-            didl_item.parent_id = item.parent.id;
-        } else {
-            didl_item.parent_id = "0";
-        }
-
-        didl_item.restricted = false;
-
-        didl_item.title = item.title;
-        didl_item.upnp_class = item.upnp_class;
-        if (item.author != null && item.author != "") {
-            var contributor = didl_item.add_creator ();
-            contributor.name = item.author;
-
-            if (item.upnp_class.has_prefix (MediaItem.VIDEO_CLASS)) {
-                contributor = didl_item.add_author ();
-                contributor.name = item.author;
-            } else if (item.upnp_class.has_prefix (MediaItem.MUSIC_CLASS)) {
-                contributor = didl_item.add_artist ();
-                contributor.name = item.author;
-            }
-        }
-
-        if (item.track_number >= 0) {
-            didl_item.track_number = item.track_number;
-        }
-
-        if (item.album != null && item.album != "") {
-            didl_item.album = item.album;
-        }
-
-        if (item.date != null && item.date != "") {
-            didl_item.date = item.date;
-        }
-
-        if (item.genre != null && item.genre != "") {
-            didl_item.genre = item.genre;
-        }
-
-        if (item.place_holder) {
-            this.http_server.add_proxy_resource (didl_item, item);
-        } else {
-            // Add the transcoded/proxy URIs first
-            this.http_server.add_resources (didl_item, item);
-
-            // then original URIs
-            bool internal_allowed;
-            internal_allowed = this.http_server.context.interface == "lo" ||
-                               this.http_server.context.host_ip == "127.0.0.1";
-            item.add_resources (didl_item, internal_allowed);
-        }
-    }
-
     private void serialize_container (MediaContainer container) throws Error {
         var didl_container = this.add_container ();
         if (container.parent != null) {
diff --git a/src/rygel/rygel-http-get-handler.vala b/src/rygel/rygel-http-get-handler.vala
index 5d11cbd..1061e36 100644
--- a/src/rygel/rygel-http-get-handler.vala
+++ b/src/rygel/rygel-http-get-handler.vala
@@ -61,8 +61,8 @@ internal abstract class Rygel.HTTPGetHandler: GLib.Object {
         }
 
         // Handle Samsung DLNA TV proprietary subtitle headers
-        if (request.msg.request_headers.get_one ("getCaptionInfo.sec") != null &&
-            request.item.subtitles.size > 0) {
+        if (request.msg.request_headers.get_one ("getCaptionInfo.sec") != null
+            && (request.item as VideoItem).subtitles.size > 0) {
                 var caption_uri = request.http_server.create_uri_for_item (
                                         request.item,
                                         -1,
diff --git a/src/rygel/rygel-http-get.vala b/src/rygel/rygel-http-get.vala
index b783102..f5be2e0 100644
--- a/src/rygel/rygel-http-get.vala
+++ b/src/rygel/rygel-http-get.vala
@@ -79,10 +79,29 @@ internal class Rygel.HTTPGet : HTTPRequest {
         yield base.find_item ();
 
         if (this.uri.thumbnail_index >= 0) {
-            this.thumbnail = this.item.thumbnails.get (
+            if (this.item is MusicItem) {
+                var music = this.item as MusicItem;
+
+                this.thumbnail = music.album_art;
+            } else if (this.item is VisualItem) {
+                var visual = this.item as VisualItem;
+
+                this.thumbnail = visual.thumbnails.get (
                                         this.uri.thumbnail_index);
+            } else {
+                throw new HTTPRequestError.NOT_FOUND (
+                                        ("No Thumbnail available for item '%s"),
+                                         this.item.id);
+            }
         } else if (this.uri.subtitle_index >= 0) {
-            this.subtitle = this.item.subtitles.get (this.uri.subtitle_index);
+            if (!(this.item is VideoItem)) {
+                throw new HTTPRequestError.NOT_FOUND (
+                                        ("No subtitles available for item '%s"),
+                                         this.item.id);
+            }
+
+            this.subtitle = (this.item as VideoItem).subtitles.get (
+                                        this.uri.subtitle_index);
         }
     }
 
diff --git a/src/rygel/rygel-http-server.vala b/src/rygel/rygel-http-server.vala
index 419e195..460a220 100644
--- a/src/rygel/rygel-http-server.vala
+++ b/src/rygel/rygel-http-server.vala
@@ -58,58 +58,13 @@ internal class Rygel.HTTPServer : Rygel.TranscodeManager, Rygel.StateMachine {
         }
     }
 
-    /* We prepend these resources into the original resource list instead of
-     * appending them because some crappy MediaRenderer/ControlPoint
-     * implemenation out there just choose the first one in the list instead of
-     * the one they can handle.
-     */
-    internal override void add_resources (DIDLLiteItem didl_item,
-                                          MediaItem    item)
-                                          throws Error {
-        // Subtitles first
-        foreach (var subtitle in item.subtitles) {
-            if (this.need_proxy (subtitle.uri)) {
-                var uri = subtitle.uri; // Save the original URI
-                var index = item.subtitles.index_of (subtitle);
-
-                subtitle.uri = this.create_uri_for_item (item,
-                                                         -1,
-                                                         index,
-                                                         null);
-                subtitle.add_didl_node (didl_item);
-
-                // Now restore the original URI
-                subtitle.uri = uri;
-            }
-        }
-
-        if (!this.http_uri_present (item)) {
-            this.add_proxy_resource (didl_item, item);
-        }
-
-        base.add_resources (didl_item, item);
-
-        // Thumbnails comes in the end
-        foreach (var thumbnail in item.thumbnails) {
-            if (this.need_proxy (thumbnail.uri)) {
-                var uri = thumbnail.uri; // Save the original URI
-                var index = item.thumbnails.index_of (thumbnail);
-
-                thumbnail.uri = this.create_uri_for_item (item,
-                                                          index,
-                                                          -1,
-                                                          null);
-                thumbnail.add_resource (didl_item, this.get_protocol ());
-
-                // Now restore the original URI
-                thumbnail.uri = uri;
-            }
-        }
-    }
-
     internal void add_proxy_resource (DIDLLiteItem didl_item,
                                       MediaItem    item)
                                       throws Error {
+        if (this.http_uri_present (item)) {
+            return;
+        }
+
         var uri = this.create_uri_for_item (item, -1, -1, null);
 
         item.add_resource (didl_item,
diff --git a/src/rygel/rygel-http-time-seek.vala b/src/rygel/rygel-http-time-seek.vala
index 2f9cc22..95c84de 100644
--- a/src/rygel/rygel-http-time-seek.vala
+++ b/src/rygel/rygel-http-time-seek.vala
@@ -35,7 +35,7 @@ internal class Rygel.HTTPTimeSeek : Rygel.HTTPSeek {
         string range, time;
         string[] range_tokens;
         int64 start = 0;
-        int64 duration = request.item.duration * SECOND;
+        int64 duration = (request.item as AudioItem).duration * SECOND;
         int64 stop = duration - SECOND;
 
         range = request.msg.request_headers.get_one ("TimeSeekRange.dlna.org");
@@ -79,7 +79,8 @@ internal class Rygel.HTTPTimeSeek : Rygel.HTTPSeek {
     }
 
     public static bool needed (HTTPGet request) {
-        return request.item.duration > 0 &&
+        return request.item is AudioItem &&
+               (request.item as AudioItem).duration > 0 &&
                (request.handler is HTTPTranscodeHandler ||
                 (request.thumbnail == null &&
                  request.subtitle == null &&
diff --git a/src/rygel/rygel-image-item.vala b/src/rygel/rygel-image-item.vala
new file mode 100644
index 0000000..a8cdd63
--- /dev/null
+++ b/src/rygel/rygel-image-item.vala
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2010 Nokia Corporation.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *                               <zeeshan ali nokia com>
+ *
+ * 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 GUPnP;
+using Gee;
+
+/**
+ * Represents an image item.
+ */
+public class Rygel.ImageItem : MediaItem, VisualItem {
+    public new const string UPNP_CLASS = "object.item.imageItem";
+
+    public int width { get; set; default = -1; }
+    public int height { get; set; default = -1; }
+    public int pixel_width { get; set; default = -1; }
+    public int pixel_height { get; set; default = -1; }
+    public int color_depth { get; set; default = -1; }
+
+    public ArrayList<Thumbnail> thumbnails { get; protected set; }
+
+    public ImageItem (string         id,
+                      MediaContainer parent,
+                      string         title,
+                      string         upnp_class = ImageItem.UPNP_CLASS) {
+        base (id, parent, title, upnp_class);
+
+        this.thumbnails = new ArrayList<Thumbnail> ();
+    }
+
+    public override bool streamable () {
+        return false;
+    }
+
+    public override void add_uri (string uri) {
+        base.add_uri (uri);
+
+        this.add_thumbnail_for_uri (uri);
+    }
+
+    internal override void add_resources (DIDLLiteItem didl_item,
+                                          bool         allow_internal)
+                                          throws Error {
+        base.add_resources (didl_item, allow_internal);
+
+        this.add_thumbnail_resources (didl_item, allow_internal);
+    }
+
+    internal override DIDLLiteResource add_resource (
+                                        DIDLLiteItem didl_item,
+                                        string?      uri,
+                                        string       protocol,
+                                        string?      import_uri = null)
+                                        throws Error {
+        var res = base.add_resource (didl_item, uri, protocol, import_uri);
+
+        this.add_visual_props (res);
+
+        return res;
+    }
+
+    internal override void add_proxy_resources (HTTPServer   server,
+                                                DIDLLiteItem didl_item)
+                                                throws Error {
+        base.add_proxy_resources (server, didl_item);
+
+        // Thumbnails comes in the end
+        this.add_thumbnail_proxy_resources (server, didl_item);
+    }
+
+    protected override ProtocolInfo get_protocol_info (string? uri,
+                                                       string  protocol) {
+        var protocol_info = base.get_protocol_info (uri, protocol);
+
+        protocol_info.dlna_flags |= DLNAFlags.INTERACTIVE_TRANSFER_MODE;
+
+        return protocol_info;
+    }
+}
diff --git a/src/rygel/rygel-item-creator.vala b/src/rygel/rygel-item-creator.vala
index 1e66853..1d46be0 100644
--- a/src/rygel/rygel-item-creator.vala
+++ b/src/rygel/rygel-item-creator.vala
@@ -71,10 +71,11 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
 
             var container = yield this.fetch_container ();
 
-            this.item = new MediaItem (didl_item.id,
-                                       container,
-                                       didl_item.title,
-                                       didl_item.upnp_class);
+            this.item = this.create_item (didl_item.id,
+                                          container,
+                                          didl_item.title,
+                                          didl_item.upnp_class);
+
             var resources = didl_item.get_resources ();
             if (resources != null) {
                 var info = resources.nth (0).data.protocol_info;
@@ -188,14 +189,35 @@ internal class Rygel.ItemCreator: GLib.Object, Rygel.StateMachine {
     }
 
     private string get_generic_mime_type () {
-        switch (this.item.upnp_class) {
-            case MediaItem.IMAGE_CLASS:
-                return "image";
-            case MediaItem.VIDEO_CLASS:
-                return "video";
-            case MediaItem.AUDIO_CLASS:
-            default:
-                return "audio";
+        if (this.item is ImageItem) {
+            return "image";
+        } else if (this.item is VideoItem) {
+            return "video";
+        } else {
+            return "audio";
+        }
+    }
+
+    private MediaItem create_item (string         id,
+                                   MediaContainer parent,
+                                   string         title,
+                                   string         upnp_class) throws Error {
+        switch (upnp_class) {
+        case ImageItem.UPNP_CLASS:
+            return new ImageItem (id, parent, title);
+        case PhotoItem.UPNP_CLASS:
+            return new PhotoItem (id, parent, title);
+        case VideoItem.UPNP_CLASS:
+            return new VideoItem (id, parent, title);
+        case AudioItem.UPNP_CLASS:
+            return new AudioItem (id, parent, title);
+        case MusicItem.UPNP_CLASS:
+            return new MusicItem (id, parent, title);
+        default:
+            throw new ContentDirectoryError.BAD_METADATA (
+                                        "Creation of item of class '%s' " +
+                                        "not supported.",
+                                         upnp_class);
         }
     }
 }
diff --git a/src/rygel/rygel-l16-transcoder.vala b/src/rygel/rygel-l16-transcoder.vala
index 5032b06..f2d7c31 100644
--- a/src/rygel/rygel-l16-transcoder.vala
+++ b/src/rygel/rygel-l16-transcoder.vala
@@ -52,7 +52,7 @@ internal class Rygel.L16Transcoder : Rygel.Transcoder {
                         ";rate=" + L16Transcoder.FREQUENCY.to_string () +
                         ";channels=" + L16Transcoder.CHANNELS.to_string ();
 
-        base (mime_type, "LPCM", MediaItem.AUDIO_CLASS);
+        base (mime_type, "LPCM", AudioItem.UPNP_CLASS);
 
         this.endianness = endianness;
     }
@@ -83,22 +83,23 @@ internal class Rygel.L16Transcoder : Rygel.Transcoder {
     }
 
     public override uint get_distance (MediaItem item) {
-        if (!item.upnp_class.has_prefix (MediaItem.AUDIO_CLASS)) {
+        if (!(item is AudioItem)) {
             return uint.MAX;
         }
 
+        var audio_item = item as AudioItem;
         var distance = uint.MIN;
 
-        if (item.sample_freq > 0) {
-            distance += (item.sample_freq - FREQUENCY).abs ();
+        if (audio_item.sample_freq > 0) {
+            distance += (audio_item.sample_freq - FREQUENCY).abs ();
         }
 
-        if (item.n_audio_channels > 0) {
-            distance += (item.n_audio_channels - CHANNELS).abs ();
+        if (audio_item.n_audio_channels > 0) {
+            distance += (audio_item.n_audio_channels - CHANNELS).abs ();
         }
 
-        if (item.bits_per_sample > 0) {
-            distance += (item.bits_per_sample - WIDTH).abs ();
+        if (audio_item.bits_per_sample > 0) {
+            distance += (audio_item.bits_per_sample - WIDTH).abs ();
         }
 
         return distance;
diff --git a/src/rygel/rygel-media-art-store.vala b/src/rygel/rygel-media-art-store.vala
index 3350ccd..4c8a024 100644
--- a/src/rygel/rygel-media-art-store.vala
+++ b/src/rygel/rygel-media-art-store.vala
@@ -54,8 +54,8 @@ public class Rygel.MediaArtStore : GLib.Object {
         return media_art_store;
     }
 
-    public Thumbnail? find_media_art (MediaItem item,
-                                      bool simple = false) throws Error {
+    public Thumbnail? find_media_art (MusicItem item,
+                                      bool      simple = false) throws Error {
         string[] types = { "track", "album", "artist", "podcast", "radio" };
         File file = null;
 
@@ -87,7 +87,7 @@ public class Rygel.MediaArtStore : GLib.Object {
         return thumb;
     }
 
-    public Thumbnail? find_media_art_any (MediaItem item) throws Error {
+    public Thumbnail? find_media_art_any (MusicItem item) throws Error {
         var thumb = this.find_media_art (item);
         if (thumb == null) {
             thumb = this.find_media_art (item, true);
@@ -97,7 +97,7 @@ public class Rygel.MediaArtStore : GLib.Object {
     }
 
     public File get_media_art_file (string    type,
-                                    MediaItem item,
+                                    MusicItem item,
                                     bool      simple = false) {
         string hash;
         string suffix;
@@ -146,22 +146,22 @@ public class Rygel.MediaArtStore : GLib.Object {
         }
     }
 
-    private string get_simple_hash (string type, MediaItem item) {
+    private string get_simple_hash (string type, MusicItem item) {
         string hash;
         switch (type) {
             case "artist":
                 case "radio":
-                hash = this.normalize_and_hash (item.author);
+                hash = this.normalize_and_hash (item.artist);
                 break;
             case "podcast":
                 hash = this.normalize_and_hash (item.title);
                 break;
             case "album":
-                hash = this.normalize_and_hash (item.author + "\t" +
+                hash = this.normalize_and_hash (item.artist + "\t" +
                                                 item.album);
                 break;
             case "track":
-                hash = this.normalize_and_hash (item.author + "\t" +
+                hash = this.normalize_and_hash (item.artist + "\t" +
                                                 item.album + "\t" +
                                                 item.title);
                 break;
@@ -172,17 +172,17 @@ public class Rygel.MediaArtStore : GLib.Object {
         return hash;
     }
 
-    private string get_hash (string type, MediaItem item) {
+    private string get_hash (string type, MusicItem item) {
         string b = null, c = null;
         switch (type) {
             case "track":
-                b = this.normalize_and_hash (item.author, false) + "-" +
+                b = this.normalize_and_hash (item.artist, false) + "-" +
                     this.normalize_and_hash (item.album, false);
                 c = this.normalize_and_hash (item.title, false);
                 break;
             case "album":
             case "artist":
-                b = this.normalize_and_hash (item.author, false);
+                b = this.normalize_and_hash (item.artist, false);
                 c = this.normalize_and_hash (item.album, false);
                 break;
             case "radio":
diff --git a/src/rygel/rygel-media-container.vala b/src/rygel/rygel-media-container.vala
index 065542f..e79d47d 100644
--- a/src/rygel/rygel-media-container.vala
+++ b/src/rygel/rygel-media-container.vala
@@ -252,9 +252,11 @@ public abstract class Rygel.MediaContainer : MediaObject {
         if (create_classes != null) {
             this.create_classes.add_all (create_classes);
         } else {
-            this.create_classes.add (MediaItem.IMAGE_CLASS);
-            this.create_classes.add (MediaItem.VIDEO_CLASS);
-            this.create_classes.add (MediaItem.AUDIO_CLASS);
+            this.create_classes.add (ImageItem.UPNP_CLASS);
+            this.create_classes.add (PhotoItem.UPNP_CLASS);
+            this.create_classes.add (VideoItem.UPNP_CLASS);
+            this.create_classes.add (AudioItem.UPNP_CLASS);
+            this.create_classes.add (MusicItem.UPNP_CLASS);
         }
     }
 
diff --git a/src/rygel/rygel-media-item.vala b/src/rygel/rygel-media-item.vala
index 53a842f..7b7cef2 100644
--- a/src/rygel/rygel-media-item.vala
+++ b/src/rygel/rygel-media-item.vala
@@ -1,7 +1,9 @@
 /*
  * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2010 Nokia Corporation.
  *
- * Author: Zeeshan Ali <zeenix gmail com>
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *                               <zeeshan ali nokia com>
  *
  * This file is part of Rygel.
  *
@@ -21,7 +23,6 @@
  */
 
 using GUPnP;
-using Gee;
 using Gst;
 
 private errordomain Rygel.MediaItemError {
@@ -31,41 +32,14 @@ private errordomain Rygel.MediaItemError {
 /**
  * Represents a media (Music, Video and Image) item.
  */
-public class Rygel.MediaItem : MediaObject {
-    public static const string IMAGE_CLASS = "object.item.imageItem";
-    public static const string PHOTO_CLASS = "object.item.imageItem.photo";
-    public static const string VIDEO_CLASS = "object.item.videoItem";
-    public static const string AUDIO_CLASS = "object.item.audioItem";
-    public static const string MUSIC_CLASS = "object.item.audioItem.musicTrack";
-
-    public string author;
-    public string album;
+public abstract class Rygel.MediaItem : MediaObject {
     public string date;
-    public string genre;
 
     // Resource info
     public string mime_type;
     public string dlna_profile;
 
     public int64 size = -1;     // Size in bytes
-    public long duration = -1;  // Duration in seconds
-    public int bitrate = -1;    // Bytes/second
-
-    // Audio/Music
-    public int sample_freq = -1;
-    public int bits_per_sample = -1;
-    public int n_audio_channels = -1;
-    public int track_number = -1;
-
-    // Image/Video
-    public int width = -1;
-    public int height = -1;
-    public int pixel_width = -1;
-    public int pixel_height = -1;
-    public int color_depth = -1;
-
-    public ArrayList<Thumbnail> thumbnails;
-    public ArrayList<Subtitle> subtitles;
 
     internal bool place_holder = false;
 
@@ -77,9 +51,6 @@ public class Rygel.MediaItem : MediaObject {
         this.parent = parent;
         this.title = title;
         this.upnp_class = upnp_class;
-
-        this.thumbnails = new ArrayList<Thumbnail> ();
-        this.subtitles = new ArrayList<Subtitle> ();
     }
 
     // Live media items need to provide a nice working implementation of this
@@ -116,57 +87,10 @@ public class Rygel.MediaItem : MediaObject {
         return this.streamable () && this.size <= 0;
     }
 
-    public bool streamable () {
-        return !this.upnp_class.has_prefix (IMAGE_CLASS);
-    }
+    public abstract bool streamable ();
 
-    public void add_uri (string uri) {
+    public virtual void add_uri (string uri) {
         this.uris.add (uri);
-
-        if (this.upnp_class.has_prefix (MediaItem.IMAGE_CLASS) ||
-            this.upnp_class.has_prefix (MediaItem.VIDEO_CLASS)) {
-            // Lets see if we can provide the thumbnails
-            var thumbnailer = Thumbnailer.get_default ();
-
-            if (thumbnailer == null) {
-                return;
-            }
-
-            try {
-                var thumb = thumbnailer.get_thumbnail (uri);
-                this.thumbnails.add (thumb);
-            } catch (Error err) {}
-        }
-
-        if (this.upnp_class.has_prefix (MediaItem.VIDEO_CLASS)) {
-            var subtitle_manager = SubtitleManager.get_default ();
-
-            if (subtitle_manager == null) {
-                return;
-            }
-
-            try {
-                var subtitle = subtitle_manager.get_subtitle (uri);
-                this.subtitles.add (subtitle);
-            } catch (Error err) {}
-        }
-    }
-
-    public void lookup_album_art () {
-        assert (this.upnp_class.has_prefix (MediaItem.AUDIO_CLASS) &&
-                this.thumbnails.size == 0);
-
-        var media_art_store = MediaArtStore.get_default ();
-        if (media_art_store == null) {
-            return;
-        }
-
-        try {
-            var thumb = media_art_store.find_media_art_any (this);
-            if (thumb != null) {
-                this.thumbnails.insert (0, thumb);
-            }
-        } catch (Error err) {};
     }
 
     internal int compare_transcoders (void *a, void *b) {
@@ -177,39 +101,12 @@ public class Rygel.MediaItem : MediaObject {
                (int) transcoder2.get_distance (this);
     }
 
-    internal void add_resources (DIDLLiteItem didl_item,
-                                 bool         allow_internal)
-                                 throws Error {
-        foreach (var subtitle in this.subtitles) {
-            var protocol = this.get_protocol_for_uri (subtitle.uri);
-
-            if (allow_internal || protocol != "internal") {
-                subtitle.add_didl_node (didl_item);
-            }
-        }
-
-        foreach (var uri in this.uris) {
-            var protocol = this.get_protocol_for_uri (uri);
-
-            if (allow_internal || protocol != "internal") {
-                this.add_resource (didl_item, uri, protocol);
-            }
-        }
-
-        foreach (var thumbnail in this.thumbnails) {
-            var protocol = this.get_protocol_for_uri (thumbnail.uri);
-
-            if (allow_internal || protocol != "internal") {
-                thumbnail.add_resource (didl_item, protocol);
-            }
-        }
-    }
-
-    internal DIDLLiteResource add_resource (DIDLLiteItem didl_item,
-                                            string?      uri,
-                                            string       protocol,
-                                            string?      import_uri = null)
-                                            throws Error {
+    internal virtual DIDLLiteResource add_resource (
+                                        DIDLLiteItem didl_item,
+                                        string?      uri,
+                                        string       protocol,
+                                        string?      import_uri = null)
+                                        throws Error {
         var res = didl_item.add_resource ();
 
         if (uri != null) {
@@ -221,16 +118,6 @@ public class Rygel.MediaItem : MediaObject {
         }
 
         res.size64 = this.size;
-        res.duration = this.duration;
-        res.bitrate = this.bitrate;
-
-        res.sample_freq = this.sample_freq;
-        res.bits_per_sample = this.bits_per_sample;
-        res.audio_channels = this.n_audio_channels;
-
-        res.width = this.width;
-        res.height = this.height;
-        res.color_depth = this.color_depth;
 
         /* Protocol info */
         res.protocol_info = this.get_protocol_info (uri, protocol);
@@ -243,12 +130,6 @@ public class Rygel.MediaItem : MediaObject {
         var item = media_object as MediaItem;
 
         switch (property) {
-        case "dc:creator":
-        case "dc:artist":
-        case "dc:author":
-            return this.compare_string_props (this.author, item.author);
-        case "upnp:album":
-            return this.compare_string_props (this.album, item.album);
         case "dc:date":
             return this.compare_by_date (item);
         default:
@@ -256,8 +137,55 @@ public class Rygel.MediaItem : MediaObject {
         }
     }
 
-    private ProtocolInfo get_protocol_info (string? uri,
-                                            string  protocol) {
+    internal virtual DIDLLiteItem serialize (DIDLLiteWriter writer)
+                                             throws Error {
+        var didl_item = writer.add_item ();
+
+        didl_item.id = this.id;
+        if (this.parent != null) {
+            didl_item.parent_id = this.parent.id;
+        } else {
+            didl_item.parent_id = "0";
+        }
+
+        didl_item.restricted = false;
+        didl_item.title = this.title;
+        didl_item.upnp_class = this.upnp_class;
+
+        /* We list proxy/transcoding resources first instead of original URIs
+         * because some crappy MediaRenderer/ControlPoint implemenation out
+         * there just choose the first one in the list instead of the one they
+         * can handle.
+         */
+        if (this.place_holder) {
+            this.add_proxy_resources (writer.http_server, didl_item);
+        } else {
+            // Add the transcoded/proxy URIs first
+            this.add_proxy_resources (writer.http_server, didl_item);
+
+            // then original URIs
+            bool internal_allowed;
+            internal_allowed = writer.http_server.context.interface == "lo" ||
+                               writer.http_server.context.host_ip ==
+                               "127.0.0.1";
+            this.add_resources (didl_item, internal_allowed);
+        }
+
+        return didl_item;
+    }
+
+    internal virtual void add_proxy_resources (HTTPServer   server,
+                                               DIDLLiteItem didl_item)
+                                               throws Error {
+        // Proxy resource for the original resources
+        server.add_proxy_resource (didl_item, this);
+
+        // Transcoding resources
+        server.add_resources (didl_item, this);
+    }
+
+    protected virtual ProtocolInfo get_protocol_info (string? uri,
+                                                      string  protocol) {
         var protocol_info = new ProtocolInfo ();
 
         protocol_info.mime_type = this.mime_type;
@@ -265,9 +193,7 @@ public class Rygel.MediaItem : MediaObject {
         protocol_info.protocol = protocol;
         protocol_info.dlna_flags = DLNAFlags.DLNA_V15;
 
-        if (this.upnp_class.has_prefix (MediaItem.IMAGE_CLASS)) {
-            protocol_info.dlna_flags |= DLNAFlags.INTERACTIVE_TRANSFER_MODE;
-        } else {
+        if (this.streamable ()) {
             protocol_info.dlna_flags |= DLNAFlags.STREAMING_TRANSFER_MODE;
         }
 
@@ -282,7 +208,7 @@ public class Rygel.MediaItem : MediaObject {
         return protocol_info;
     }
 
-    private string get_protocol_for_uri (string uri) throws Error {
+    internal string get_protocol_for_uri (string uri) throws Error {
         var scheme = Uri.parse_scheme (uri);
         if (scheme == null) {
             throw new MediaItemError.BAD_URI (_("Bad URI: %s"), uri);
@@ -305,6 +231,18 @@ public class Rygel.MediaItem : MediaObject {
         }
     }
 
+    protected virtual void add_resources (DIDLLiteItem didl_item,
+                                          bool         allow_internal)
+                                          throws Error {
+        foreach (var uri in this.uris) {
+            var protocol = this.get_protocol_for_uri (uri);
+
+            if (allow_internal || protocol != "internal") {
+                this.add_resource (didl_item, uri, protocol);
+            }
+        }
+    }
+
     private int compare_by_date (MediaItem item) {
         if (this.date == null) {
             return -1;
diff --git a/src/rygel/rygel-mp2ts-transcoder.vala b/src/rygel/rygel-mp2ts-transcoder.vala
index 43f578c..772e8fb 100644
--- a/src/rygel/rygel-mp2ts-transcoder.vala
+++ b/src/rygel/rygel-mp2ts-transcoder.vala
@@ -51,7 +51,7 @@ internal class Rygel.MP2TSTranscoder : Rygel.Transcoder {
     private MP2TSProfile profile;
 
     public MP2TSTranscoder (MP2TSProfile profile) {
-        base ("video/mpeg", PROFILES[profile], MediaItem.VIDEO_CLASS);
+        base ("video/mpeg", PROFILES[profile], VideoItem.UPNP_CLASS);
 
         this.profile = profile;
     }
@@ -78,22 +78,23 @@ internal class Rygel.MP2TSTranscoder : Rygel.Transcoder {
     }
 
     public override uint get_distance (MediaItem item) {
-        if (!item.upnp_class.has_prefix (MediaItem.VIDEO_CLASS)) {
+        if (!(item is VideoItem)) {
             return uint.MAX;
         }
 
+        var video_item = item as VideoItem;
         var distance = uint.MIN;
 
-        if (item.bitrate > 0) {
-            distance += (item.bitrate - BITRATE).abs ();
+        if (video_item.bitrate > 0) {
+            distance += (video_item.bitrate - BITRATE).abs ();
         }
 
-        if (item.width > 0) {
-            distance += (item.width - WIDTH[this.profile]).abs ();
+        if (video_item.width > 0) {
+            distance += (video_item.width - WIDTH[this.profile]).abs ();
         }
 
-        if (item.height > 0) {
-            distance += (item.height - HEIGHT[this.profile]).abs ();
+        if (video_item.height > 0) {
+            distance += (video_item.height - HEIGHT[this.profile]).abs ();
         }
 
         return distance;
@@ -120,9 +121,15 @@ internal class Rygel.MP2TSTranscoder : Rygel.Transcoder {
         int pixel_w;
         int pixel_h;
 
-        if (item.pixel_width > 0 && item.pixel_height > 0) {
-            pixel_w = item.width * HEIGHT[this.profile] * item.pixel_width;
-            pixel_h = item.height * WIDTH[this.profile] * item.pixel_height;
+        var video_item = item as VideoItem;
+
+        if (video_item.pixel_width > 0 && video_item.pixel_height > 0) {
+            pixel_w = video_item.width *
+                      HEIGHT[this.profile] *
+                      video_item.pixel_width;
+            pixel_h = video_item.height *
+                      WIDTH[this.profile] *
+                      video_item.pixel_height;
         } else {
             // Original pixel-ratio not provided, lets just use 1:1
             pixel_w = 1;
diff --git a/src/rygel/rygel-mp3-transcoder.vala b/src/rygel/rygel-mp3-transcoder.vala
index 0c2f494..73e6151 100644
--- a/src/rygel/rygel-mp3-transcoder.vala
+++ b/src/rygel/rygel-mp3-transcoder.vala
@@ -39,7 +39,7 @@ internal class Rygel.MP3Transcoder : Rygel.Transcoder {
     private MP3Layer layer;
 
     public MP3Transcoder (MP3Layer layer) {
-        base ("audio/mpeg", "MP3", MediaItem.AUDIO_CLASS);
+        base ("audio/mpeg", "MP3", AudioItem.UPNP_CLASS);
 
         this.layer = layer;
     }
@@ -65,14 +65,15 @@ internal class Rygel.MP3Transcoder : Rygel.Transcoder {
     }
 
     public override uint get_distance (MediaItem item) {
-        if (!item.upnp_class.has_prefix (MediaItem.AUDIO_CLASS)) {
+        if (!(item is AudioItem)) {
             return uint.MAX;
         }
 
+        var audio_item = item as AudioItem;
         var distance = uint.MIN;
 
-        if (item.bitrate > 0) {
-            distance += (item.bitrate - BITRATE).abs ();
+        if (audio_item.bitrate > 0) {
+            distance += (audio_item.bitrate - BITRATE).abs ();
         }
 
         return distance;
diff --git a/src/rygel/rygel-music-item.vala b/src/rygel/rygel-music-item.vala
new file mode 100644
index 0000000..3885c13
--- /dev/null
+++ b/src/rygel/rygel-music-item.vala
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2010 Nokia Corporation.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *                               <zeeshan ali nokia com>
+ *
+ * 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 GUPnP;
+using Gst;
+
+/**
+ * Represents a music item.
+ */
+public class Rygel.MusicItem : AudioItem {
+    public new const string UPNP_CLASS = "object.item.audioItem.musicTrack";
+
+    public string artist;
+    public string album;
+    public string genre;
+    public int track_number = -1;
+
+    public Thumbnail album_art;
+
+    public MusicItem (string         id,
+                      MediaContainer parent,
+                      string         title,
+                      string         upnp_class = MusicItem.UPNP_CLASS) {
+        base (id, parent, title, upnp_class);
+    }
+
+    public void lookup_album_art () {
+        assert (this.album_art == null);
+
+        var media_art_store = MediaArtStore.get_default ();
+        if (media_art_store == null) {
+            return;
+        }
+
+        try {
+            this.album_art = media_art_store.find_media_art_any (this);
+        } catch (Error err) {};
+    }
+
+    internal override void add_resources (DIDLLiteItem didl_item,
+                                          bool         allow_internal)
+                                         throws Error {
+        base.add_resources (didl_item, allow_internal);
+
+        if (this.album_art != null) {
+            var protocol = this.get_protocol_for_uri (this.album_art.uri);
+
+            if (allow_internal || protocol != "internal") {
+                album_art.add_resource (didl_item, protocol);
+            }
+        }
+    }
+
+    internal override int compare_by_property (MediaObject media_object,
+                                               string      property) {
+        var item = media_object as MusicItem;
+
+        switch (property) {
+        case "dc:artist":
+            return this.compare_string_props (this.artist, item.artist);
+        case "upnp:album":
+            return this.compare_string_props (this.album, item.album);
+        default:
+            return base.compare_by_property (item, property);
+        }
+    }
+
+    internal override DIDLLiteItem serialize (DIDLLiteWriter writer)
+                                             throws Error {
+        var didl_item = base.serialize (writer);
+
+        if (this.artist != null && this.artist != "") {
+            var contributor = didl_item.add_artist ();
+            contributor.name = this.artist;
+        }
+
+        if (this.track_number >= 0) {
+            didl_item.track_number = this.track_number;
+        }
+
+        if (this.album != null && this.album != "") {
+            didl_item.album = this.album;
+        }
+
+        if (this.genre != null && this.genre != "") {
+            didl_item.genre = this.genre;
+        }
+
+        return didl_item;
+    }
+
+    internal override void add_proxy_resources (HTTPServer   server,
+                                                DIDLLiteItem didl_item)
+                                                throws Error {
+        base.add_proxy_resources (server, didl_item);
+
+        // Album-art URI comes in the end
+        if (this.album_art != null && server.need_proxy (this.album_art.uri)) {
+            var uri = album_art.uri; // Save the original URI
+
+            album_art.uri = server.create_uri_for_item (this, 0, -1, null);
+            album_art.add_resource (didl_item, server.get_protocol ());
+
+            // Now restore the original URI
+            album_art.uri = uri;
+        }
+    }
+}
diff --git a/src/rygel/rygel-photo-item.vala b/src/rygel/rygel-photo-item.vala
new file mode 100644
index 0000000..df46cb1
--- /dev/null
+++ b/src/rygel/rygel-photo-item.vala
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2010 Nokia Corporation.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *                               <zeeshan ali nokia com>
+ *
+ * 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 GUPnP;
+using Gee;
+using Gst;
+
+/**
+ * Represents a photo item.
+ */
+public class Rygel.PhotoItem : ImageItem {
+    public new const string UPNP_CLASS = "object.item.imageItem.photo";
+
+    public string creator;
+
+    public PhotoItem (string         id,
+                      MediaContainer parent,
+                      string         title,
+                      string         upnp_class = PhotoItem.UPNP_CLASS) {
+        base (id, parent, title, upnp_class);
+    }
+
+    internal override int compare_by_property (MediaObject media_object,
+                                               string      property) {
+        var item = media_object as PhotoItem;
+
+        switch (property) {
+        case "dc:creator":
+            return this.compare_string_props (this.creator, item.creator);
+        default:
+            return base.compare_by_property (item, property);
+        }
+    }
+
+    internal override DIDLLiteItem serialize (DIDLLiteWriter writer)
+                                             throws Error {
+        var didl_item = base.serialize (writer);
+
+        if (this.creator != null && this.creator != "") {
+            var contributor = didl_item.add_creator ();
+            contributor.name = this.creator;
+        }
+
+        return didl_item;
+    }
+}
diff --git a/src/rygel/rygel-relational-expression.vala b/src/rygel/rygel-relational-expression.vala
index 90a5d1e..24d3348 100644
--- a/src/rygel/rygel-relational-expression.vala
+++ b/src/rygel/rygel-relational-expression.vala
@@ -47,12 +47,12 @@ public class Rygel.RelationalExpression :
             var container = media_object as MediaContainer;
             return this.compare_create_class (container);
         case "dc:creator":
-            if (!(media_object is MediaItem)) {
+            if (!(media_object is PhotoItem)) {
                 return false;
             }
 
-            var item = media_object as MediaItem;
-            return this.compare_string (item.author);
+            var photo = media_object as PhotoItem;
+            return this.compare_string (photo.creator);
         default:
             return false;
         }
diff --git a/src/rygel/rygel-transcode-manager.vala b/src/rygel/rygel-transcode-manager.vala
index a8f83b8..f6dff1e 100644
--- a/src/rygel/rygel-transcode-manager.vala
+++ b/src/rygel/rygel-transcode-manager.vala
@@ -88,9 +88,8 @@ internal abstract class Rygel.TranscodeManager : GLib.Object {
                                                 int        subtitle_index,
                                                 string?    transcode_target);
 
-    public virtual void add_resources (DIDLLiteItem didl_item,
-                                       MediaItem    item)
-                                       throws Error {
+    public void add_resources (DIDLLiteItem didl_item, MediaItem item)
+                               throws Error {
         var list = new GLib.List<Transcoder> ();
 
         foreach (var transcoder in this.transcoders) {
diff --git a/src/rygel/rygel-transcoder.vala b/src/rygel/rygel-transcoder.vala
index fe6c8c2..3b9e394 100644
--- a/src/rygel/rygel-transcoder.vala
+++ b/src/rygel/rygel-transcoder.vala
@@ -79,7 +79,7 @@ internal abstract class Rygel.Transcoder : GLib.Object {
         protocol_info.dlna_flags = DLNAFlags.STREAMING_TRANSFER_MODE |
                                    DLNAFlags.SENDER_PACED |
                                    DLNAFlags.DLNA_V15;
-        if (item.duration > 0) {
+        if (item is AudioItem && (item as AudioItem).duration > 0) {
             protocol_info.dlna_operation = DLNAOperation.TIMESEEK;
         } else {
             protocol_info.dlna_operation = DLNAOperation.NONE;
diff --git a/src/rygel/rygel-video-item.vala b/src/rygel/rygel-video-item.vala
new file mode 100644
index 0000000..4a2ef74
--- /dev/null
+++ b/src/rygel/rygel-video-item.vala
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2010 Nokia Corporation.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *                               <zeeshan ali nokia com>
+ *
+ * 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 GUPnP;
+using Gee;
+using Gst;
+
+/**
+ * Represents a video item.
+ */
+public class Rygel.VideoItem : AudioItem, VisualItem {
+    public new const string UPNP_CLASS = "object.item.videoItem";
+
+    public string author;
+
+    public int width { get; set; default = -1; }
+    public int height { get; set; default = -1; }
+    public int pixel_width { get; set; default = -1; }
+    public int pixel_height { get; set; default = -1; }
+    public int color_depth { get; set; default = -1; }
+
+    public ArrayList<Thumbnail> thumbnails { get; protected set; }
+    public ArrayList<Subtitle> subtitles;
+
+    public VideoItem (string         id,
+                      MediaContainer parent,
+                      string         title,
+                      string         upnp_class = VideoItem.UPNP_CLASS) {
+        base (id, parent, title, upnp_class);
+
+        this.thumbnails = new ArrayList<Thumbnail> ();
+        this.subtitles = new ArrayList<Subtitle> ();
+    }
+
+    public override bool streamable () {
+        return true;
+    }
+
+    public override void add_uri (string uri) {
+        base.add_uri (uri);
+
+        this.add_thumbnail_for_uri (uri);
+
+        var subtitle_manager = SubtitleManager.get_default ();
+
+        if (subtitle_manager != null) {
+            try {
+                var subtitle = subtitle_manager.get_subtitle (uri);
+                this.subtitles.add (subtitle);
+            } catch (Error err) {}
+        }
+    }
+
+    internal override void add_resources (DIDLLiteItem didl_item,
+                                          bool         allow_internal)
+                                          throws Error {
+        foreach (var subtitle in this.subtitles) {
+            var protocol = this.get_protocol_for_uri (subtitle.uri);
+
+            if (allow_internal || protocol != "internal") {
+                subtitle.add_didl_node (didl_item);
+            }
+        }
+
+        base.add_resources (didl_item, allow_internal);
+
+        add_thumbnail_resources (didl_item, allow_internal);
+    }
+
+    internal override DIDLLiteResource add_resource (
+                                        DIDLLiteItem didl_item,
+                                        string?      uri,
+                                        string       protocol,
+                                        string?      import_uri = null)
+                                        throws Error {
+        var res = base.add_resource (didl_item, uri, protocol, import_uri);
+
+        this.add_visual_props (res);
+
+        return res;
+    }
+
+    internal override int compare_by_property (MediaObject media_object,
+                                               string      property) {
+        var item = media_object as VideoItem;
+
+        switch (property) {
+        case "dc:author":
+            return this.compare_string_props (this.author, item.author);
+        default:
+            return base.compare_by_property (item, property);
+        }
+    }
+
+    internal override DIDLLiteItem serialize (DIDLLiteWriter writer)
+                                             throws Error {
+        var didl_item = base.serialize (writer);
+
+        if (this.author != null && this.author != "") {
+            var contributor = didl_item.add_author ();
+            contributor.name = this.author;
+        }
+
+        return didl_item;
+    }
+
+    internal override void add_proxy_resources (HTTPServer   server,
+                                                DIDLLiteItem didl_item)
+                                                throws Error {
+        // Subtitles first
+        foreach (var subtitle in this.subtitles) {
+            if (server.need_proxy (subtitle.uri)) {
+                var uri = subtitle.uri; // Save the original URI
+                var index = this.subtitles.index_of (subtitle);
+
+                subtitle.uri = server.create_uri_for_item (this,
+                                                           -1,
+                                                           index,
+                                                           null);
+                subtitle.add_didl_node (didl_item);
+
+                // Now restore the original URI
+                subtitle.uri = uri;
+            }
+        }
+
+        base.add_proxy_resources (server, didl_item);
+
+        // Thumbnails comes in the end
+        this.add_thumbnail_proxy_resources (server, didl_item);
+    }
+}
diff --git a/src/rygel/rygel-visual-item.vala b/src/rygel/rygel-visual-item.vala
new file mode 100644
index 0000000..e57132f
--- /dev/null
+++ b/src/rygel/rygel-visual-item.vala
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2010 Nokia Corporation.
+ *
+ * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *                               <zeeshan ali nokia com>
+ *
+ * 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 GUPnP;
+using Gee;
+using Gst;
+
+/**
+ * An interface that visual (video and image) items must implement.
+ */
+public interface Rygel.VisualItem : MediaItem {
+    public abstract int width { get; set; }
+    public abstract int height { get; set; }
+    public abstract int pixel_width { get; set; }
+    public abstract int pixel_height { get; set; }
+    public abstract int color_depth { get; set; }
+
+    public abstract ArrayList<Thumbnail> thumbnails { get; protected set; }
+
+    internal void add_thumbnail_for_uri (string uri) {
+        // Lets see if we can provide the thumbnails
+        var thumbnailer = Thumbnailer.get_default ();
+
+        if (thumbnailer != null) {
+            try {
+                var thumb = thumbnailer.get_thumbnail (uri);
+                this.thumbnails.add (thumb);
+            } catch (Error err) {}
+        }
+    }
+
+    internal void add_thumbnail_resources (DIDLLiteItem didl_item,
+                                           bool         allow_internal)
+                                           throws Error {
+        foreach (var thumbnail in this.thumbnails) {
+            var protocol = this.get_protocol_for_uri (thumbnail.uri);
+
+            if (allow_internal || protocol != "internal") {
+                thumbnail.add_resource (didl_item, protocol);
+            }
+        }
+    }
+
+    internal void add_visual_props (DIDLLiteResource res) {
+        res.width = this.width;
+        res.height = this.height;
+        res.color_depth = this.color_depth;
+    }
+
+    internal void add_thumbnail_proxy_resources (HTTPServer   server,
+                                                 DIDLLiteItem didl_item)
+                                                 throws Error {
+        foreach (var thumbnail in this.thumbnails) {
+            if (server.need_proxy (thumbnail.uri)) {
+                var uri = thumbnail.uri; // Save the original URI
+                var index = this.thumbnails.index_of (thumbnail);
+
+                thumbnail.uri = server.create_uri_for_item (this,
+                                                            index,
+                                                            -1,
+                                                            null);
+                thumbnail.add_resource (didl_item, server.get_protocol ());
+
+                // Now restore the original URI
+                thumbnail.uri = uri;
+            }
+        }
+    }
+}
diff --git a/src/rygel/rygel-wma-transcoder.vala b/src/rygel/rygel-wma-transcoder.vala
index f356f6c..c18fc04 100644
--- a/src/rygel/rygel-wma-transcoder.vala
+++ b/src/rygel/rygel-wma-transcoder.vala
@@ -28,7 +28,7 @@ internal class Rygel.WMATranscoder : Rygel.Transcoder {
     private const string CONVERT_SINK_PAD = "convert-sink-pad";
 
     public WMATranscoder () {
-        base ("audio/x-wma", "WMA", MediaItem.AUDIO_CLASS);
+        base ("audio/x-wma", "WMA", AudioItem.UPNP_CLASS);
     }
 
     public override Element create_source (MediaItem item,
@@ -52,14 +52,15 @@ internal class Rygel.WMATranscoder : Rygel.Transcoder {
     }
 
     public override uint get_distance (MediaItem item) {
-        if (!item.upnp_class.has_prefix (MediaItem.AUDIO_CLASS)) {
+        if (!(item is AudioItem)) {
             return uint.MAX;
         }
 
+        var audio_item = item as AudioItem;
         var distance = uint.MIN;
 
-        if (item.bitrate > 0) {
-            distance += (item.bitrate - BITRATE).abs ();
+        if (audio_item.bitrate > 0) {
+            distance += (audio_item.bitrate - BITRATE).abs ();
         }
 
         return distance;
diff --git a/src/rygel/rygel-wmv-transcoder.vala b/src/rygel/rygel-wmv-transcoder.vala
index 5f3b375..18a4744 100644
--- a/src/rygel/rygel-wmv-transcoder.vala
+++ b/src/rygel/rygel-wmv-transcoder.vala
@@ -32,7 +32,7 @@ internal class Rygel.WMVTranscoder : Rygel.Transcoder {
     private const string VIDEO_SCALE = "videoscale";
 
     public WMVTranscoder () {
-        base ("video/x-ms-wmv", "WMVHIGH_FULL", MediaItem.VIDEO_CLASS);
+        base ("video/x-ms-wmv", "WMVHIGH_FULL", VideoItem.UPNP_CLASS);
     }
 
     public override Element create_source (MediaItem item,
@@ -49,22 +49,25 @@ internal class Rygel.WMVTranscoder : Rygel.Transcoder {
         if (resource == null)
             return null;
 
-        resource.width = item.width;
-        resource.height = item.height;
+        var video_item = item as VideoItem;
+
+        resource.width = video_item.width;
+        resource.height = video_item.height;
         resource.bitrate = (VIDEO_BITRATE + WMATranscoder.BITRATE) * 1000 / 8;
 
         return resource;
     }
 
     public override uint get_distance (MediaItem item) {
-        if (!item.upnp_class.has_prefix (MediaItem.VIDEO_CLASS)) {
+        if (!(item is VideoItem)) {
             return uint.MAX;
         }
 
+        var video_item = item as VideoItem;
         var distance = uint.MIN;
 
-        if (item.bitrate > 0) {
-            distance += (item.bitrate - BITRATE).abs ();
+        if (video_item.bitrate > 0) {
+            distance += (video_item.bitrate - BITRATE).abs ();
         }
 
         return distance;



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