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);
}
}
}
// 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;
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) {
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 {
}
// 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);
}
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)
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);
}
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) {
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 () &&
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);
+ }
}
}
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);
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 () {
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);
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) {
}
}
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);
}
// 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);
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) {
}
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) {
public RequestCallback callback;
public int width;
public int height;
+ public bool cropped;
public void unqueue () {
if (Factory.get_instance ().server != null)
}
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)));
}
}
[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);