/* 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);
}
}
}