[rygel] core: Add album art as a special type of thumbnail



commit 81baade3ecc2b38daf8df9452c2bd075a567f016
Author: Jens Georg <mail jensge org>
Date:   Thu May 20 20:31:24 2010 +0200

    core: Add album art as a special type of thumbnail

 src/rygel/Makefile.am                 |    2 +
 src/rygel/rygel-album-art.vala        |   37 ++++++
 src/rygel/rygel-http-get-handler.vala |   10 +-
 src/rygel/rygel-media-art-store.vala  |  229 +++++++++++++++++++++++++++++++++
 src/rygel/rygel-media-item.vala       |   21 +++
 src/rygel/rygel-thumbnail.vala        |    4 +-
 6 files changed, 297 insertions(+), 6 deletions(-)
---
diff --git a/src/rygel/Makefile.am b/src/rygel/Makefile.am
index 956d2e1..c700e68 100644
--- a/src/rygel/Makefile.am
+++ b/src/rygel/Makefile.am
@@ -74,6 +74,8 @@ VAPI_SOURCE_FILES = rygel-configuration.vala \
 		    rygel-media-item.vala \
 		    rygel-thumbnail.vala \
 		    rygel-thumbnailer.vala \
+			rygel-album-art.vala \
+			rygel-media-art-store.vala \
 		    rygel-subtitle.vala \
 		    rygel-subtitle-manager.vala \
 		    rygel-browse.vala \
diff --git a/src/rygel/rygel-album-art.vala b/src/rygel/rygel-album-art.vala
new file mode 100644
index 0000000..d72882c
--- /dev/null
+++ b/src/rygel/rygel-album-art.vala
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using GUPnP;
+
+public class Rygel.AlbumArt : Thumbnail {
+    public AlbumArt() {
+        base ();
+    }
+
+    internal override DIDLLiteResource? add_resource (DIDLLiteItem didl_item,
+                                                      string       protocol) {
+        Xml.Node* node = didl_item.xml_node;
+        node->new_child (null, "upnp:albumArtURI", this.uri);
+
+        return null;
+    }
+}
diff --git a/src/rygel/rygel-http-get-handler.vala b/src/rygel/rygel-http-get-handler.vala
index 21caf69..f23ab17 100644
--- a/src/rygel/rygel-http-get-handler.vala
+++ b/src/rygel/rygel-http-get-handler.vala
@@ -48,11 +48,13 @@ internal abstract class Rygel.HTTPGetHandler: GLib.Object {
         var didl_item = didl_writer.add_item ();
         try {
             var resource = this.add_resource (didl_item, request);
-            var tokens = resource.protocol_info.to_string ().split (":", 4);
-            assert (tokens.length == 4);
+            if (resource != null) {
+                var tokens = resource.protocol_info.to_string ().split (":", 4);
+                assert (tokens.length == 4);
 
-            request.msg.response_headers.append ("contentFeatures.dlna.org",
-                                                 tokens[3]);
+                request.msg.response_headers.append ("contentFeatures.dlna.org",
+                                                     tokens[3]);
+            }
         } catch (Error err) {
             warning ("Received request for 'contentFeatures.dlna.org' but " +
                        "failed to provide the value in response headers");
diff --git a/src/rygel/rygel-media-art-store.vala b/src/rygel/rygel-media-art-store.vala
new file mode 100644
index 0000000..bb5e322
--- /dev/null
+++ b/src/rygel/rygel-media-art-store.vala
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2010 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+internal errordomain MediaArtStoreError {
+    NO_DIR,
+    NO_MEDIA_ART
+}
+
+public class Rygel.MediaArtStore : GLib.Object {
+    private const string PLACEHOLDER_HASH = "7215ee9c7d9dc229d2921a40e899ec5f";
+    private const string invalid_chars = "()[]<>{}_! #$^&*+=|\\/\"'?~";
+    private const string convert_chars = "\t";
+    private const string block_pattern = "%s[^%s]*%s";
+    private const string[] blocks = { "()", "{}", "[]", "<>" };
+    private static MediaArtStore media_art_store;
+    private static bool first_time = true;
+    private Regex char_remove_regex;
+    private Regex char_convert_regex;
+    private Regex space_compress_regex;
+    private Regex[] block_regexes;
+
+    private string directory;
+
+    private MediaArtStore () throws MediaArtStoreError {
+        var dir = Path.build_filename (Environment.get_user_cache_dir (),
+                                       "media-art");
+        var file = File.new_for_path (dir);
+
+        if (!file.query_exists (null)) {
+            var message = _("Failed to find media-art directory");
+
+            throw new MediaArtStoreError.NO_DIR (message);
+        }
+
+        this.directory = dir;
+        try {
+            var regex_string = Regex.escape_string (invalid_chars);
+            char_remove_regex = new Regex ("[%s]".printf (regex_string));
+            regex_string = Regex.escape_string (convert_chars);
+            char_convert_regex = new Regex ("[%s]".printf (regex_string));
+            space_compress_regex = new Regex ("\\s+");
+            block_regexes = new Regex[0];
+
+            foreach (var block in blocks) {
+                var block_re = block_pattern.printf (
+                                  Regex.escape_string ("%C".printf (block[0])),
+                                  Regex.escape_string ("%C".printf (block[1])),
+                                  Regex.escape_string ("%C".printf (block[1])));
+                block_regexes += new Regex (block_re);
+            }
+        } catch (RegexError error) {
+            assert_not_reached ();
+        }
+    }
+
+    public static MediaArtStore? get_default () {
+        if (first_time) {
+            try {
+                media_art_store = new MediaArtStore ();
+            } catch (MediaArtStoreError error) {
+                warning (_("No media art available: %s"), error.message);
+            }
+        }
+
+        first_time = false;
+
+        return media_art_store;
+    }
+
+    public Thumbnail? find_media_art_any (MediaItem item) throws Error {
+        var thumb = find_media_art (item);
+        if (thumb == null) {
+            thumb = find_media_art (item, true);
+        }
+
+        return thumb;
+    }
+
+    public File get_media_art_file (string    type,
+                                    MediaItem item,
+                                    bool      simple = false) {
+        string hash;
+
+        if (simple) {
+            hash = get_simple_hash (type, item);
+        } else {
+            hash = get_hash (type, item);
+        }
+        var file_path = "%s-%s.jpeg".printf (type, hash);
+
+        var path = Path.build_filename (this.directory, file_path);
+
+        return File.new_for_path (path);
+    }
+
+    private string normalize_and_hash (string? input, bool utf8_only = true) {
+        string normalized = " ";
+        if (input != null && input != "") {
+            if (utf8_only) {
+                normalized = input;
+            } else {
+                normalized = albumart_strip_invalid_entities (input);
+            }
+            normalized = normalized.normalize (-1, NormalizeMode.ALL);
+        }
+
+        return Checksum.compute_for_string (ChecksumType.MD5,
+                                            normalized.down ());
+    }
+
+    private string get_simple_hash (string type, MediaItem item) {
+        string hash;
+        switch (type) {
+            case "artist":
+                case "radio":
+                hash = normalize_and_hash (item.author);
+                break;
+            case "podcast":
+                hash = normalize_and_hash (item.title);
+                break;
+            case "album":
+                hash = normalize_and_hash (item.author + "\t" +
+                        item.album);
+                break;
+            case "track":
+                hash = normalize_and_hash (item.author + "\t" +
+                        item.album + "\t" +
+                        item.title);
+                break;
+            default:
+                assert_not_reached ();
+        }
+
+        return hash;
+    }
+
+    private string get_hash (string type, MediaItem item) {
+        string b = null, c = null;
+        switch (type) {
+            case "track":
+                b = normalize_and_hash (item.author, false) + "-" +
+                    normalize_and_hash (item.album, false);
+                c = normalize_and_hash (item.title, false);
+                break;
+            case "album":
+            case "artist":
+                b = normalize_and_hash (item.author, false);
+                c = normalize_and_hash (item.album, false);
+                break;
+            case "radio":
+            case "podcast":
+                b = normalize_and_hash (item.title, false);
+                c = PLACEHOLDER_HASH;
+                break;
+        }
+
+        return "%s-%s".printf (b, c);
+    }
+
+    public Thumbnail? find_media_art (MediaItem item,
+                                      bool simple = false) throws Error {
+        string[] types = { "track", "album", "artist", "podcast", "radio" };
+        File file = null;
+
+        foreach (var type in types) {
+            file = get_media_art_file (type, item, simple);
+            if (file.query_exists (null)) {
+                break;
+            } else {
+                file = null;
+            }
+        }
+
+        if (file == null) {
+            return null;
+        }
+
+        var info = file.query_info (FILE_ATTRIBUTE_ACCESS_CAN_READ,
+                                    FileQueryInfoFlags.NONE,
+                                    null);
+        if (!info.get_attribute_boolean (FILE_ATTRIBUTE_ACCESS_CAN_READ)) {
+            return null;
+        }
+
+        var thumb = new AlbumArt ();
+        thumb.uri = file.get_uri ();
+
+        return thumb;
+    }
+
+    string albumart_strip_invalid_entities (string original) {
+        string p;
+
+        p = original;
+
+        try {
+            foreach (var re in block_regexes) {
+                p = re.replace_literal (p, -1, 0, "");
+            }
+
+            p = char_remove_regex.replace (p, -1, 0, "");
+            p = char_convert_regex.replace (p, -1, 0, " ");
+            p = space_compress_regex.replace (p, -1, 0, " ");
+
+            return p;
+        } catch (RegexError error) {
+            assert_not_reached ();
+        }
+    }
+}
diff --git a/src/rygel/rygel-media-item.vala b/src/rygel/rygel-media-item.vala
index 34c95df..e7ada12 100644
--- a/src/rygel/rygel-media-item.vala
+++ b/src/rygel/rygel-media-item.vala
@@ -152,6 +152,27 @@ public class Rygel.MediaItem : MediaObject {
         }
     }
 
+    public void lookup_album_art () {
+        if (!this.upnp_class.has_prefix (MediaItem.AUDIO_CLASS)) {
+            return;
+        }
+
+
+        if (!(this.thumbnails.size > 0 && this.thumbnails[0] is AlbumArt)) {
+            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) {
         var transcoder1 = (Transcoder) a;
         var transcoder2 = (Transcoder) b;
diff --git a/src/rygel/rygel-thumbnail.vala b/src/rygel/rygel-thumbnail.vala
index da308e4..c83af73 100644
--- a/src/rygel/rygel-thumbnail.vala
+++ b/src/rygel/rygel-thumbnail.vala
@@ -35,8 +35,8 @@ public class Rygel.Thumbnail : Rygel.IconInfo {
         this.dlna_profile = dlna_profile;
     }
 
-    internal DIDLLiteResource add_resource (DIDLLiteItem didl_item,
-                                            string       protocol) {
+    internal virtual DIDLLiteResource? add_resource (DIDLLiteItem didl_item,
+                                                     string       protocol) {
         var res = didl_item.add_resource ();
 
         res.uri = this.uri;



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