Poster downloading update
authorPhilipp Zabel <philipp.zabel@gmail.com>
Sun, 8 Aug 2010 13:18:37 +0000 (15:18 +0200)
committerPhilipp Zabel <philipp.zabel@gmail.com>
Sun, 8 Aug 2010 14:06:18 +0000 (16:06 +0200)
Remove the thumbnail variants of the poster downloader's Fetch and the
poster factory's queue methods, instead add width and height parameters.
The IMDb poster downloader now downloads the small poster image directly
from the title page when only a thumbnail is requested. The full poster
images are scaled down to 288x400.
The poster factory now creates thumbnails from the downloaded posters
(nearly) according to the freedesktop.org thumbnail spec: they are stored
as jpeg files and the sizes of normal and large thumbnails are optimized
for the list view and poster grid, respectively.

src/movie-list-view.vala
src/movie-window.vala
src/poster/google-poster-downloader.vala
src/poster/imdb-poster-downloader.vala
src/poster/movie-poster-factory.vala
src/poster/poster-downloader-interface.vala

index efb1c2e..c1f3bcd 100644 (file)
@@ -307,23 +307,33 @@ public class MovieListView : PannableArea {
                for (int i = a; i <= b; i++) {
                        var path = new TreePath.from_indices (i);
                        TreeIter iter;
-                       if (store.get_iter (out iter, path)) {
-                               Movie movie;
-                               store.get (iter, MovieListStore.Columns.MOVIE, out movie);
-                               if (movie != null) {
-                                       if (poster_mode_) {
-                                               if (movie.poster == null || movie.poster.small == null) try {
-                                                       poster_factory.queue_thumbnail (movie, Poster.SMALL_WIDTH, Poster.SMALL_HEIGHT, false, receive_poster_small);
-                                               } catch (Error e) {
-                                                       warning ("Failed to queue poster request: %s\n", e.message);
-                                               }
-                                       } else {
-                                               if (movie.poster == null || movie.poster.icon == null) try {
-                                                       poster_factory.queue_thumbnail (movie, Poster.ICON_WIDTH, Poster.ICON_HEIGHT, false, receive_poster_icon);
-                                               } catch (Error e) {
-                                                       warning ("Failed to queue poster request: %s\n", e.message);
-                                               }
-                                       }
+                       if (!store.get_iter (out iter, path))
+                               continue;
+
+                       Movie movie;
+                       store.get (iter, MovieListStore.Columns.MOVIE, out movie);
+                       if (movie == null)
+                               continue;
+
+                       if (poster_mode_) {
+                               if (movie.poster == null || movie.poster.small == null) try {
+                                       poster_factory.queue (movie,
+                                                             Poster.SMALL_WIDTH,
+                                                             Poster.SMALL_HEIGHT,
+                                                             true,
+                                                             receive_poster_small);
+                               } catch (Error e) {
+                                       warning ("Failed to queue poster request: %s\n", e.message);
+                               }
+                       } else {
+                               if (movie.poster == null || movie.poster.icon == null) try {
+                                       poster_factory.queue (movie,
+                                                             Poster.ICON_WIDTH,
+                                                             Poster.ICON_HEIGHT,
+                                                             true,
+                                                             receive_poster_icon);
+                               } catch (Error e) {
+                                       warning ("Failed to queue poster request: %s\n", e.message);
                                }
                        }
                }
index bb92083..a4433b9 100644 (file)
@@ -43,29 +43,6 @@ public class MovieWindow : StackableWindow {
                // Poster
                image = new Image ();
 
-               if (movie.poster != null && movie.poster.large != null) {
-                       image.pixbuf = movie.poster.large;
-               } else {
-                       if (movie.poster != null && movie.poster.small != null) {
-                               // FIXME
-                               image.pixbuf = movie.poster.small.scale_simple (288, 400, Gdk.InterpType.BILINEAR);
-                       } else {
-                               // FIXME
-                               if (no_poster == null) try {
-                                       no_poster = new Gdk.Pixbuf.from_file ("/usr/share/icons/hicolor/64x64/hildon/general_no_thumbnail.png");
-                               } catch (Error e) {
-                                       critical ("Missing general_no_thumbnail icon: %s\n", e.message);
-                               }
-                               image.pixbuf = no_poster;
-                       }
-                       try {
-                               poster_factory = MoviePoster.Factory.get_instance ();
-                               poster_factory.queue (movie, receive_poster);
-                       } catch (Error e) {
-                               warning ("Failed to queue poster request: %s\n", e.message);
-                       }
-               }
-
                title_label = new Label (title_label_markup (movie));
                title_label.wrap = true;
                title_label.use_markup = true;
@@ -115,6 +92,29 @@ public class MovieWindow : StackableWindow {
                menu.movie_deleted.connect (() => { destroy (); });
                Gdk.Screen.get_default ().size_changed.connect (on_orientation_changed);
                movie.notify.connect (this.on_movie_changed);
+
+               if (movie.poster != null && movie.poster.large != null) {
+                       image.pixbuf = movie.poster.large;
+               } else {
+                       if (movie.poster != null && movie.poster.small != null) {
+                               // FIXME
+                               image.pixbuf = movie.poster.small.scale_simple (288, 400, Gdk.InterpType.HYPER);
+                       } else {
+                               // FIXME
+                               if (no_poster == null) try {
+                                       no_poster = new Gdk.Pixbuf.from_file ("/usr/share/icons/hicolor/64x64/hildon/general_no_thumbnail.png");
+                               } catch (Error e) {
+                                       critical ("Missing general_no_thumbnail icon: %s\n", e.message);
+                               }
+                               image.pixbuf = no_poster;
+                       }
+                       try {
+                               poster_factory = MoviePoster.Factory.get_instance ();
+                               poster_factory.queue (movie, 288, 400, false, receive_poster);
+                       } catch (Error e) {
+                               warning ("Failed to queue poster request: %s\n", e.message);
+                       }
+               }
        }
 
        private void update_title (Movie movie) {
index b251bc5..97b0a1f 100644 (file)
@@ -73,18 +73,18 @@ public class GooglePosterDownload : Object {
        private string cache_filename;
        private bool cancelled = false;
 
-       public GooglePosterDownload (string title, string year, bool thumbnail, int _handle, GooglePosterDownloader _downloader) {
+       public GooglePosterDownload (string title, string year, int width, int height, int _handle, GooglePosterDownloader _downloader) {
                var search = title + "+" + year + "+movie+poster";
 
                handle = _handle;
                downloader = _downloader;
                session = downloader.session;
 
-               var message = new GoogleImageSearch (search, thumbnail);
+               var message = new GoogleImageSearch (search, (width < 128 && height < 128));
                message.got_poster_uri.connect (on_got_poster_uri);
                session.queue_message (message, google_search_finished);
 
-               if (thumbnail) {
+               if (width < 128 && height < 128) {
                        // FIXME
                        cache_dir = Path.build_filename (Environment.get_tmp_dir(), "cinaest-thumbnails");
                } else {
@@ -179,16 +179,8 @@ public class GooglePosterDownloader : Object, PosterDownloader {
        }
 
        // Implement the PosterDownloader interface
-       public int Fetch (string title, string year, string kind) throws DBus.Error {
-               var download = new GooglePosterDownload (title, year, false, ++fetch_handle, this);
-
-               downloads.append (download);
-
-               return fetch_handle;
-       }
-
-       public int FetchThumbnail (string title, string year, string kind) throws DBus.Error {
-               var download = new GooglePosterDownload (title, year, true, ++fetch_handle, this);
+       public int Fetch (string title, string year, string kind, int width, int height) throws DBus.Error {
+               var download = new GooglePosterDownload (title, year, width, height, ++fetch_handle, this);
 
                downloads.append (download);
 
index 4827bd0..3b7271b 100644 (file)
@@ -22,10 +22,8 @@ namespace Hildon {
 }
 
 namespace Poster {
-       public const int SMALL_WIDTH = (800 - 2*Hildon.MARGIN_DOUBLE - 4*Hildon.MARGIN_HALF)/5;
-       public const int SMALL_HEIGHT = (420 - Hildon.MARGIN_HALF)/2;
-       public const int ICON_WIDTH = 46;
-       public const int ICON_HEIGHT = 64;
+       public const int WIDTH = 288;
+       public const int HEIGHT = 400;
 }
 
 // A single IMDb poster image search (parsing and retrieval of the poster image URI)
@@ -34,14 +32,16 @@ public class IMDbMessage : Soup.Message {
        public int year;
 
        public string poster_uri = null;
-       private bool thumbnail;
+       public int width;
+       public int height;
 
-       public IMDbMessage (string title_, int year_, bool thumbnail_ = false) {
+       public IMDbMessage (string title_, int year_, int width_, int height_) {
                Object (method: "GET");
 
                title = title_;
                year = year_;
-               thumbnail = thumbnail_;
+               width = width_;
+               height = height_;
                string url = "http://www.imdb.com/find?s=tt&q=%s (%d)".printf (convert (title, -1, "iso8859-1", "utf-8"), year);
                uri = new Soup.URI (url);
        }
@@ -51,31 +51,29 @@ public class IMDbMessage : Soup.Message {
 public class IMDbPosterDownload : Object {
        private IMDbPosterDownloader downloader;
        private Soup.Session session;
-       private string cache_dir;
-       private string cache_filename;
-       private bool thumbnail;
+       private string poster_path;
        private bool cancelled = false;
 
        public int handle;
 
-       public IMDbPosterDownload (string title, string year, bool thumbnail_, int _handle, IMDbPosterDownloader _downloader) {
+       public IMDbPosterDownload (string title, string year, int width, int height, int _handle, IMDbPosterDownloader _downloader) {
                handle = _handle;
                downloader = _downloader;
                session = downloader.session;
-               thumbnail = thumbnail_;
 
-               var message = new IMDbMessage (title, year.to_int (), thumbnail);
-               session.queue_message (message, title_page_callback);
+               print ("[%d] LOOKING FOR \"%s (%s)\"\n", handle, title, year);
 
-               if (thumbnail) {
-                       // FIXME
-                       cache_dir = Path.build_filename (Environment.get_tmp_dir(), "cinaest-thumbnails");
-               } else {
-                       cache_dir = Path.build_filename (Environment.get_user_cache_dir(), "media-art");
-               }
-               cache_filename = "movie-" +
-                                Checksum.compute_for_string (ChecksumType.MD5, (title).down ()) + "-" +
-                                Checksum.compute_for_string (ChecksumType.MD5, (year).down ()) + ".jpeg";
+               // Define poster path according to the Media Art Storage Spec (http://live.gnome.org/MediaArtStorageSpec)
+
+       //      poster_path = Hildon.albumart_get_path (title, year, "movie");
+               poster_path = Path.build_filename (Environment.get_user_cache_dir (),
+                                                  "media-art",
+                                                  "movie-%s-%s.jpeg".printf (
+                             Checksum.compute_for_string (ChecksumType.MD5, title.down ()),
+                             Checksum.compute_for_string (ChecksumType.MD5, year)));
+
+               var message = new IMDbMessage (title, year.to_int (), width, height);
+               session.queue_message (message, title_page_callback);
        }
 
        private void title_page_callback (Soup.Session session, Soup.Message message) {
@@ -86,15 +84,16 @@ public class IMDbPosterDownload : Object {
                        return;
                }
 
+               var msg = (IMDbMessage) message;
+
                if (message.uri.path == "/find") {
                        print ("[%d] AMBIGUOUS RESULTS: %s\n", handle, message.uri.to_string (false));
 
-                       var msg = (IMDbMessage) message;
-
-                       var re3 = new Regex ("<a href=\"(/title/[^\"]*/)\"[^>]*>([^<]*)</a> *\\(([0-9]*)");
+                       // Search page: title page link
+                       var re_sp_tpl = new Regex ("<a href=\"(/title/[^\"]*/)\"[^>]*>([^<]*)</a> *\\(([0-9]*)");
 
                        MatchInfo match;
-                       if (re3.match ((string) message.response_body.data, 0, out match)) {
+                       if (re_sp_tpl.match ((string) message.response_body.data, 0, out match)) {
                                do {
                                        print ("[%d] POTENTIAL RESULT: %s (%s)\n", handle, match.fetch (2), match.fetch (3));
                                        if (msg.title.down () == match.fetch (2).down () &&
@@ -114,15 +113,29 @@ public class IMDbPosterDownload : Object {
 
                print ("[%d] GOT TITLE PAGE FOR %s\n", handle, message.uri.path);
 
-               MatchInfo match;
-               if (downloader.re1.match ((string) message.response_body.data, 0, out match)) {
-                       string url = "http://www.imdb.com" + match.fetch (1);
-                       print ("[%d] FOUND PHOTO PAGE URL: %s\n", handle, url);
-                       message.uri = new Soup.URI (url);
-                       session.queue_message (message, this.photo_page_callback);
+               // Can we get away with the low-res poster thumbnail on the title page?
+               if (msg.width <= 95 && msg.height <= 140) {
+                       MatchInfo match;
+                       if (downloader.re_tp_pth.match ((string) message.response_body.data, 0, out match)) {
+                               string url = match.fetch (1);
+                               print ("[%d] POSTER THUMBNAIL URL: %s\n", handle, url);
+                               message.uri = new Soup.URI (url);
+                               session.queue_message (message, this.image_callback);
+                       } else {
+                               print ("[%d] NO POSTER THUMBNAIL AVAILABLE\n", handle);
+                               downloader.finished (this, null);
+                       }
                } else {
-                       print ("[%d] NO POSTER AVAILABLE\n", handle);
-                       downloader.finished (this, null);
+                       MatchInfo match;
+                       if (downloader.re_tp_ppl.match ((string) message.response_body.data, 0, out match)) {
+                               string url = "http://www.imdb.com" + match.fetch (1);
+                               print ("[%d] FOUND PHOTO PAGE URL: %s\n", handle, url);
+                               message.uri = new Soup.URI (url);
+                               session.queue_message (message, this.photo_page_callback);
+                       } else {
+                               print ("[%d] NO POSTER AVAILABLE\n", handle);
+                               downloader.finished (this, null);
+                       }
                }
        }
 
@@ -136,7 +149,7 @@ public class IMDbPosterDownload : Object {
                print ("[%d] GOT PHOTO PAGE %s\n", handle, message.uri.path);
 
                MatchInfo match;
-               if (downloader.re2.match ((string) message.response_body.data, 0, out match)) {
+               if (downloader.re_pp_pim.match ((string) message.response_body.data, 0, out match)) {
                        string url = match.fetch (1);
                        print ("[%d] FOUND IMAGE URL: %s\n", handle, url);
                        message.uri = new Soup.URI (url);
@@ -153,44 +166,34 @@ public class IMDbPosterDownload : Object {
 
                print ("[%d] Downloaded poster: %s\n", handle, message.uri.to_string (false));
 
-               // Define cache path according to the Media Art Storage Spec (http://live.gnome.org/MediaArtStorageSpec)
-               string cache_path = Path.build_filename (cache_dir, cache_filename);
+               // Make sure the media-art directory is available
+               DirUtils.create_with_parents (Path.get_dirname (poster_path), 0770);
 
-               // Make sure the directory .album_arts is available
-               DirUtils.create_with_parents (cache_dir, 0770);
-
-               if (FileUtils.set_contents (cache_path + ".part",
+               if (FileUtils.set_contents (poster_path + ".part",
                                            (string) message.response_body.data,
                                            (ssize_t) message.response_body.length)) {
-                       if (!thumbnail) {
-                               FileUtils.rename (cache_path + ".part", cache_path);
-                       } else {
-                               // Scale and crop
-                               var p1 = new Gdk.Pixbuf.from_file (cache_path + ".part");
-                               var p2 = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, Poster.SMALL_WIDTH, Poster.SMALL_HEIGHT);
-                               double xscale = (double)Poster.SMALL_WIDTH / (double)p1.width;
-                               double yscale = (double)Poster.SMALL_HEIGHT / (double)p1.height;
-                               double scale;
-                               int ofs_x = 0;
-                               int ofs_y = 0;
-                               if (xscale > yscale) {
-                                       scale = xscale;
-                                       ofs_y = -(int) (p1.height*scale - Poster.SMALL_HEIGHT)/2;
-                               } else {
-                                       scale = yscale;
-                                       ofs_x = -(int) (p1.width*scale - Poster.SMALL_WIDTH)/2;
-                               }
-                               p1.scale (p2, 0, 0, Poster.SMALL_WIDTH, Poster.SMALL_HEIGHT, ofs_x, ofs_y, scale, scale, Gdk.InterpType.BILINEAR);
-                               print ("[%d] Scaled %d x %d --> %d x %d (%+d,%+d)\n", handle, p1.width, p1.height, p2.width, p2.height, ofs_x, ofs_y);
-                               p2.save (cache_path, "png", null);
-                               FileUtils.unlink (cache_path + ".part");
+                       var pixbuf = new Gdk.Pixbuf.from_file (poster_path + ".part");
+                       int width = pixbuf.width;
+                       int height = pixbuf.height;
+
+                       if (width > Poster.WIDTH || height > Poster.HEIGHT) {
+                               // Scale down
+                               width = int.min (Poster.WIDTH,
+                                                (pixbuf.width * height + pixbuf.height / 2) / pixbuf.height);
+                               height = int.min ((pixbuf.height * width + pixbuf.width / 2) / pixbuf.width,
+                                                 Poster.HEIGHT);
+                               pixbuf = pixbuf.scale_simple (width, height, Gdk.InterpType.BILINEAR);
+                               pixbuf.save (poster_path + ".part", "jpeg", null);
                        }
-                       print ("[%d] Stored as: %s\n", handle, cache_path);
+
+                       FileUtils.rename (poster_path + ".part", poster_path);
+
+                       print ("[%d] Stored as: %s (%dx%d)\n", handle, poster_path, width, height);
+                       downloader.finished (this, poster_path);
                } else {
                        stdout.printf ("[%d] Failed to store poster\n", handle);
+                       downloader.finished (this, null);
                }
-
-               downloader.finished (this, cache_path);
        }
 
        public void cancel () {
@@ -207,8 +210,9 @@ public class IMDbPosterDownloader : Object, PosterDownloader {
        private uint source_id;
 
        public Soup.SessionAsync session;
-       public Regex re1;
-       public Regex re2;
+       public Regex re_tp_ppl;
+       public Regex re_tp_pth;
+       public Regex re_pp_pim;
 
        public IMDbPosterDownloader () {
                loop = new MainLoop (null);
@@ -217,8 +221,12 @@ public class IMDbPosterDownloader : Object, PosterDownloader {
                session.max_conns = 40;
                session.max_conns_per_host = 20;
                try {
-                       re1 = new Regex ("\"(/rg/action-box-title/primary-photo/media/[^\"]*)\"");
-                       re2 = new Regex ("\"(http://ia.media-imdb.com/images[^\"]*)\"");
+                       // Title page: photo page link
+                       re_tp_ppl = new Regex ("\"(/rg/action-box-title/primary-photo/media/[^\"]*)\"");
+                       // Title page: poster thumbnail
+                       re_tp_pth = new Regex ("<img[^>]*id=\"primary-poster\"[^>]*src=\"(http://ia.media-imdb.com/images[^\"]*)\"");
+                       // Photo page: poster image
+                       re_pp_pim = new Regex ("\"(http://ia.media-imdb.com/images[^\"]*)\"");
                } catch (RegexError e) {
                }
        }
@@ -246,9 +254,9 @@ public class IMDbPosterDownloader : Object, PosterDownloader {
                loop.run ();
        }
 
-       public void finished (IMDbPosterDownload download, string? cache_path) {
-               if (cache_path != null)
-                       fetched (download.handle, cache_path);
+       public void finished (IMDbPosterDownload download, string? poster_path) {
+               if (poster_path != null)
+                       fetched (download.handle, poster_path);
                else
                        failed (download.handle);
                downloads.remove (download);
@@ -256,18 +264,9 @@ public class IMDbPosterDownloader : Object, PosterDownloader {
        }
 
        // Implement the PosterDownloader interface
-       public int Fetch (string title, string year, string kind) throws DBus.Error {
-               print ("Fetch (\"%s\", \"%s\", \"%s\") = %d\n", title, year, kind, fetch_handle+1);
-               var download = new IMDbPosterDownload (title, year, false, ++fetch_handle, this);
-
-               downloads.append (download);
-
-               return fetch_handle;
-       }
-
-       public int FetchThumbnail (string title, string year, string kind) throws DBus.Error {
-               print ("FetchThumbnail (\"%s\", \"%s\", \"%s\") = %d\n", title, year, kind, fetch_handle+1);
-               var download = new IMDbPosterDownload (title, year, true, ++fetch_handle, this);
+       public int Fetch (string title, string year, string kind, int width, int height) throws DBus.Error {
+               print ("Fetch (\"%s\", \"%s\", \"%s\", %d, %d) = %d\n", title, year, kind, width, height, fetch_handle+1);
+               var download = new IMDbPosterDownload (title, year, width, height, ++fetch_handle, this);
 
                downloads.append (download);
 
index 35c4ba2..6e4d410 100644 (file)
@@ -64,53 +64,110 @@ namespace MoviePoster {
                        return the_factory;
                }
 
-               public int queue (Movie movie, RequestCallback callback) throws Error {
-                       string path = get_path (movie);
+               public int queue (Movie movie, int width, int height, bool cropped, RequestCallback callback) throws Error {
+                       string path = get_poster_path (movie);
 
-                       foreach (Request request in requests)
-                               if (request.movie == movie)
+                       foreach (Request request in requests) {
+                               if (request.movie == movie &&
+                                   request.width >= width &&
+                                   request.height >= height)
                                        return 0;
+                       }
 
-                       if (FileUtils.test (path, FileTest.IS_REGULAR)) {
-                               // TODO: make this async?
-                               var pixbuf = new Gdk.Pixbuf.from_file_at_scale (path, 288, 400, true);
+                       // TODO: make this async / put out of process
+                       Gdk.Pixbuf pixbuf;
+                       if (width <= 256 && height <= 256)
+                               pixbuf = request_thumbnail (path, width, height, cropped);
+                       else
+                               pixbuf = pixbuf_from_path (path, width, height, cropped);
+                       if (pixbuf != null) {
                                callback (pixbuf, movie);
-                       } else if (server != null && download_posters) {
+                               return 0;
+                       }
+
+                       if (server != null && download_posters) {
                                var request = new Request ();
 
-                               request.handle = server.Fetch (movie.title, movie.year.to_string (), "movie");
+                               request.handle = server.Fetch (movie.title, movie.year.to_string (), "movie", width, height);
                                request.movie = movie;
                                request.callback = callback;
-                               request.width = 288;
-                               request.height = 400;
+                               request.width = width;
+                               request.height = height;
+                               request.cropped = cropped;
                                requests.append (request);
                        }
                        return 0;
                }
 
-               public int queue_thumbnail (Movie movie, uint width, uint height, bool cropped, RequestCallback callback) throws Error {
-                       string path = get_path_thumbnail (movie);
+               private Gdk.Pixbuf? request_thumbnail (string path, int width, int height, bool cropped) {
+                       string thumbnail_path = get_thumbnail_path (path, width, height);
 
-                       foreach (Request request in requests)
-                               if (request.movie == movie)
-                                       return 0;
+                       var pixbuf = pixbuf_from_path (thumbnail_path, width, height, cropped);
+                       if (pixbuf == null) {
+                               pixbuf = pixbuf_from_path (path, width, height, cropped);
+                               if (pixbuf != null)
+                                       pixbuf.save (thumbnail_path, "jpeg", null);
+                       }
+
+                       return pixbuf;
+               }
 
+               private Gdk.Pixbuf? pixbuf_from_path (string path, int width, int height, bool cropped) {
                        if (FileUtils.test (path, FileTest.IS_REGULAR)) {
-                               // TODO: make this async?
-                               var pixbuf = new Gdk.Pixbuf.from_file_at_scale (path, (int) width, (int) height, true);
+                               var pixbuf = new Gdk.Pixbuf.from_file (path);
 
-                               callback (pixbuf, movie);
-                       } else if (server != null && download_posters) {
-                               var request = new Request ();
+                               if (pixbuf.width == width && pixbuf.height == height)
+                                       return pixbuf;
 
-                               request.handle = server.FetchThumbnail (movie.title, movie.year.to_string (), "movie");
-                               request.movie = movie;
-                               request.callback = callback;
-                               request.width = (int) width;
-                               request.height = (int) height;
-                               requests.append (request);
+                               if (!cropped && (pixbuf.width == width || pixbuf.height == height))
+                                       return pixbuf;
+
+                               if (!download_posters ||
+                                          (pixbuf.width >= width && pixbuf.height >= height) ||
+                                          (pixbuf.width >= Poster.SMALL_WIDTH && pixbuf.height >= Poster.SMALL_HEIGHT)) {
+                                       // Scale to fit
+                                       return scale_to_fit (pixbuf, width, height, cropped);
+
+                               }
+                       }
+
+                       return null;
+               }
+
+               private Gdk.Pixbuf scale_to_fit (Gdk.Pixbuf source, int width, int height, bool cropped) {
+                       Gdk.InterpType interp_type;
+
+                       if (width > source.width && height > source.height)
+                               interp_type = Gdk.InterpType.HYPER;
+                       else
+                               interp_type = Gdk.InterpType.BILINEAR;
+
+                       if (cropped) {
+                               double xscale = (double) width / (double) source.width;
+                               double yscale = (double) height / (double) source.height;
+                               double scale;
+                               int ofs_x = 0;
+                               int ofs_y = 0;
+                               if (xscale > yscale) {
+                                       scale = xscale;
+                                       ofs_y = (int) (height - source.height * scale) / 2;
+                               } else {
+                                       scale = yscale;
+                                       ofs_x = (int) (width - source.width * scale) / 2;
+                               }
+
+                               var dest = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, width, height);
+                               source.scale (dest, 0, 0, width, height, ofs_x, ofs_y, scale, scale, interp_type);
+
+                               return dest;
+                       } else {
+                               width = int.min (width,
+                                                (source.width * height + source.height / 2) / source.height);
+                               height = int.min ((source.height * width + source.width / 2) / source.width,
+                                                 height);
+
+                               return source.scale_simple (width, height, interp_type);
                        }
-                       return 0;
                }
 
                private void on_poster_fetched (dynamic DBus.Object server, int handle, string path) {
@@ -123,15 +180,11 @@ namespace MoviePoster {
                        }
                        if (request == null)
                                return;
-                       try {
-                               var pixbuf = new Gdk.Pixbuf.from_file_at_size (path, request.width, request.height);
 
-                               requests.remove (request);
-                               request.callback (pixbuf, request.movie);
-                               return;
-                       } catch (Error e) {
-                               warning ("Failed to open poster: %s\n", e.message);
-                       }
+                       var pixbuf = request_thumbnail (path, request.width, request.height, request.cropped);
+
+                       requests.remove (request);
+                       request.callback (pixbuf, request.movie);
                }
 
                private void on_poster_failed (dynamic DBus.Object server, int handle) {
@@ -197,6 +250,7 @@ namespace MoviePoster {
                public RequestCallback callback;
                public int width;
                public int height;
+               public bool cropped;
 
                public void unqueue () {
                        if (Factory.get_instance ().server != null)
@@ -210,23 +264,30 @@ namespace MoviePoster {
        }
 
        public static bool is_cached (Movie movie) {
-               string filename = get_path (movie);
+               string filename = get_poster_path (movie);
                if (FileUtils.test (filename, FileTest.IS_REGULAR))
                        return true;
                else
                        return false;
        }
 
-       public static string get_path (Movie movie) {
-               return Path.build_filename (Environment.get_user_cache_dir (), "media-art", "movie-" +
-                                           Checksum.compute_for_string (ChecksumType.MD5, movie.title.down ()) + "-" +
-                                           Checksum.compute_for_string (ChecksumType.MD5, movie.year.to_string ()) + ".jpeg");
+       public static string get_poster_path (Movie movie) {
+       //      return Hildon.albumart_get_path (movie.title, movie.year.to_string (), "movie");
+               return Path.build_filename (Environment.get_user_cache_dir (),
+                                           "media-art",
+                                           "movie-%s-%s.jpeg".printf (
+                      Checksum.compute_for_string (ChecksumType.MD5, movie.title.down ()),
+                      Checksum.compute_for_string (ChecksumType.MD5, movie.year.to_string ())));
        }
 
-       public static string get_path_thumbnail (Movie movie) {
-               return Path.build_filename (Environment.get_tmp_dir (), "cinaest-thumbnails", "movie-" +
-                                           Checksum.compute_for_string (ChecksumType.MD5, movie.title.down ()) + "-" +
-                                           Checksum.compute_for_string (ChecksumType.MD5, movie.year.to_string ()) + ".jpeg");
+       public static string get_thumbnail_path (string poster_path, int width, int height) {
+               bool large = width > 128 || height > 128;
+
+               return Path.build_filename (Environment.get_home_dir (),
+                                           ".thumbnails",
+                                           large ? "large" : "normal",
+                                           "%s.jpeg".printf (
+                      Checksum.compute_for_string (ChecksumType.MD5, "file://" + poster_path)));
        }
 }
 
index 607a82a..fc253d5 100644 (file)
@@ -18,8 +18,7 @@
 
 [DBus (name = "org.maemo.movieposter.Provider", signals = "fetched")]
 public interface PosterDownloader {
-       public abstract int Fetch (string title, string year, string kind) throws DBus.Error;
-       public abstract int FetchThumbnail (string title, string year, string kind) throws DBus.Error;
+       public abstract int Fetch (string title, string year, string kind, int width, int height) throws DBus.Error;
        public abstract void Unqueue (int handle) throws DBus.Error;
        public signal void fetched (int handle, string path);
        public signal void failed (int handle);