/* This file is part of Cinaest. * * Copyright (C) 2010 Philipp Zabel * * Cinaest is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Cinaest 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Cinaest. If not, see . */ namespace Hildon { public const int MARGIN_DOUBLE = 16; public const int MARGIN_HALF = 4; } namespace Poster { 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 class IMDbMessage : Soup.Message { public string title; public int year; public string poster_uri = null; public int width; public int height; public IMDbMessage (string title_, int year_, int width_, int height_) { Object (method: "GET"); title = title_; year = year_; 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); } } // Encapsulation of a single poster download (IMDb title search query and image file download) public class IMDbPosterDownload : Object { private IMDbPosterDownloader downloader; private Soup.Session session; private string poster_path; private bool cancelled = false; public int handle; public IMDbPosterDownload (string title, string year, int width, int height, int _handle, IMDbPosterDownloader _downloader) { handle = _handle; downloader = _downloader; session = downloader.session; print ("[%d] LOOKING FOR \"%s (%s)\"\n", handle, title, year); // 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) { if (cancelled || message.status_code != Soup.KnownStatusCode.OK) { print ("[%d] NO POSTER FOR %s (CODE %u)\n", handle, message.uri.to_string (false), message.status_code); downloader.finished (this, null); return; } var msg = (IMDbMessage) message; if (message.uri.path == "/find") { print ("[%d] AMBIGUOUS RESULTS: %s\n", handle, message.uri.to_string (false)); // Search page: title page link var re_sp_tpl = new Regex ("]*>([^<]*) *\\(([0-9]*)"); MatchInfo 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 () && msg.year == match.fetch (3).to_int ()) { string url = "http://www.imdb.com" + match.fetch (1); print ("[%d] CHOSE RESULT URL: %s\n", handle, url); message.uri = new Soup.URI (url); session.queue_message (message, this.title_page_callback); return; } } while (match.next ()); } print ("[%d] NO MATCH\n", handle); downloader.finished (this, null); return; } print ("[%d] GOT TITLE PAGE FOR %s\n", handle, message.uri.path); // 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 { 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); } } } private void photo_page_callback (Soup.Session session, Soup.Message message) { if (cancelled || message.status_code != Soup.KnownStatusCode.OK) { downloader.finished (this, null); return; } print ("[%d] GOT PHOTO PAGE %s\n", handle, message.uri.path); MatchInfo 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); session.queue_message (message, this.image_callback); } } private void image_callback (Soup.Session session, Soup.Message message) { if (cancelled || message.status_code != Soup.KnownStatusCode.OK) { downloader.finished (this, null); return; } print ("[%d] Downloaded poster: %s\n", handle, message.uri.to_string (false)); // Make sure the media-art directory is available DirUtils.create_with_parents (Path.get_dirname (poster_path), 0770); if (FileUtils.set_contents (poster_path + ".part", (string) message.response_body.data, (ssize_t) message.response_body.length)) { 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); } 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); } } public void cancel () { print ("[%d] Cancelled\n", handle); cancelled = true; } } // The D-Bus service to manage poster downloads public class IMDbPosterDownloader : Object, PosterDownloader { private MainLoop loop; private int fetch_handle = 1; private List downloads = null; private uint source_id; public Soup.SessionAsync session; public Regex re_tp_ppl; public Regex re_tp_pth; public Regex re_pp_pim; public IMDbPosterDownloader () { loop = new MainLoop (null); session = new Soup.SessionAsync (); session.max_conns = 40; session.max_conns_per_host = 20; try { // 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 ("]*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) { } } public void timeout_quit () { // With every change we reset the timer to 3min if (source_id != 0) { Source.remove (source_id); } source_id = Timeout.add_seconds (180, quit); } private bool quit () { loop.quit (); print ("Timeout. Quitting with %u remaining downloads.\n", downloads.length ()); foreach (IMDbPosterDownload download in downloads) failed (download.handle); // One-shot only return false; } public void run () { loop.run (); } public void finished (IMDbPosterDownload download, string? poster_path) { if (poster_path != null) fetched (download.handle, poster_path); else failed (download.handle); downloads.remove (download); timeout_quit (); } // Implement the PosterDownloader interface 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 fetch_handle; } public void Unqueue (int handle) throws DBus.Error { print ("Unqueue (%d)\n", handle); IMDbPosterDownload download = null; foreach (IMDbPosterDownload d in downloads) { if (d.handle == handle) { download = d; d.cancel (); break; } } if (download != null) { downloads.remove (download); } } static void main () { try { var conn = DBus.Bus.get (DBus.BusType.SESSION); dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus"); // Try to register service in session bus uint res = bus.request_name ("org.maemo.movieposter.IMDb", (uint) 0); if (res == DBus.RequestNameReply.PRIMARY_OWNER) { // Start server var server = new IMDbPosterDownloader (); conn.register_object ("/org/maemo/movieposter/IMDb", server); server.timeout_quit (); server.run (); } } catch (Error e) { error ("Oops: %s\n", e.message); } } }