You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

741 lines
23 KiB

#!/usr/bin/env python
#-*- coding:utf-8 -*-
#author:doganaydin /// forked from dbr/Ben
#project:themoviedb
#repository:http://github.com/doganaydin/themoviedb
#license: LGPLv2 http://www.gnu.org/licenses/lgpl.html
"""An interface to the themoviedb.org API"""
__author__ = "doganaydin"
13 years ago
__version__ = "0.5"
13 years ago
config = {}
13 years ago
def configure(api_key):
config['apikey'] = api_key
config['urls'] = {}
config['urls']['movie.search'] = "http://api.themoviedb.org/2.1/Movie.search/en/xml/%(apikey)s/%%s" % (config)
config['urls']['movie.getInfo'] = "http://api.themoviedb.org/2.1/Movie.getInfo/en/xml/%(apikey)s/%%s" % (config)
config['urls']['media.getInfo'] = "http://api.themoviedb.org/2.1/Media.getInfo/en/xml/%(apikey)s/%%s/%%s" % (config)
config['urls']['imdb.lookUp'] = "http://api.themoviedb.org/2.1/Movie.imdbLookup/en/xml/%(apikey)s/%%s" % (config)
config['urls']['movie.browse'] = "http://api.themoviedb.org/2.1/Movie.browse/en-US/xml/%(apikey)s?%%s" % (config)
import os, struct, urllib, urllib2, xml.etree.cElementTree as ElementTree
class TmdBaseError(Exception):
pass
13 years ago
class TmdNoResults(TmdBaseError):
pass
13 years ago
class TmdHttpError(TmdBaseError):
pass
13 years ago
class TmdXmlError(TmdBaseError):
pass
13 years ago
class TmdConfigError(TmdBaseError):
pass
def opensubtitleHashFile(name):
"""Hashes a file using OpenSubtitle's method.
> In natural language it calculates: size + 64bit chksum of the first and
> last 64k (even if they overlap because the file is smaller than 128k).
A slightly more Pythonic version of the Python solution on..
http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes
"""
longlongformat = 'q'
bytesize = struct.calcsize(longlongformat)
f = open(name, "rb")
filesize = os.path.getsize(name)
fhash = filesize
if filesize < 65536 * 2:
13 years ago
raise ValueError("File size must be larger than %s bytes (is %s)" % (65536 * 2, filesize))
for x in range(65536 / bytesize):
buf = f.read(bytesize)
(l_value,) = struct.unpack(longlongformat, buf)
fhash += l_value
fhash = fhash & 0xFFFFFFFFFFFFFFFF # to remain as 64bit number
f.seek(max(0, filesize - 65536), 0)
for x in range(65536 / bytesize):
buf = f.read(bytesize)
(l_value,) = struct.unpack(longlongformat, buf)
fhash += l_value
fhash = fhash & 0xFFFFFFFFFFFFFFFF
f.close()
return "%016x" % fhash
class XmlHandler:
"""Deals with retrieval of XML files from API"""
def __init__(self, url):
self.url = url
def _grabUrl(self, url):
try:
urlhandle = urllib2.urlopen(url)
except IOError, errormsg:
raise TmdHttpError(errormsg)
if urlhandle.code >= 400:
raise TmdHttpError("HTTP status code was %d" % urlhandle.code)
return urlhandle.read()
def getEt(self):
xml = self._grabUrl(self.url)
try:
et = ElementTree.fromstring(xml)
except SyntaxError, errormsg:
raise TmdXmlError(errormsg)
return et
class SearchResults(list):
"""Stores a list of Movie's that matched the search"""
def __repr__(self):
return "<Search results: %s>" % (list.__repr__(self))
class MovieResult(dict):
"""A dict containing the information about a specific search result"""
def __repr__(self):
return "<MovieResult: %s (%s)>" % (self.get("name"), self.get("released"))
def info(self):
"""Performs a MovieDb.getMovieInfo search on the current id, returns
a Movie object
"""
cur_id = self['id']
info = MovieDb().getMovieInfo(cur_id)
return info
class Movie(dict):
"""A dict containing the information about the film"""
def __repr__(self):
return "<MovieResult: %s (%s)>" % (self.get("name"), self.get("released"))
class Categories(dict):
"""Stores category information"""
def set(self, category_et):
"""Takes an elementtree Element ('category') and stores the url,
using the type and name as the dict key.
For example:
<category type="genre" url="http://themoviedb.org/encyclopedia/category/80" name="Crime"/>
..becomes:
categories['genre']['Crime'] = 'http://themoviedb.org/encyclopedia/category/80'
"""
_type = category_et.get("type")
name = category_et.get("name")
url = category_et.get("url")
self.setdefault(_type, {})[name] = url
self[_type][name] = url
class Studios(dict):
"""Stores category information"""
def set(self, studio_et):
"""Takes an elementtree Element ('studio') and stores the url,
using the name as the dict key.
For example:
<studio url="http://www.themoviedb.org/encyclopedia/company/20" name="Miramax Films"/>
..becomes:
studios['name'] = 'http://www.themoviedb.org/encyclopedia/company/20'
"""
name = studio_et.get("name")
url = studio_et.get("url")
self[name] = url
class Countries(dict):
"""Stores country information"""
def set(self, country_et):
"""Takes an elementtree Element ('country') and stores the url,
using the name and code as the dict key.
For example:
<country url="http://www.themoviedb.org/encyclopedia/country/223" name="United States of America" code="US"/>
..becomes:
countries['code']['name'] = 'http://www.themoviedb.org/encyclopedia/country/223'
"""
code = country_et.get("code")
name = country_et.get("name")
url = country_et.get("url")
self.setdefault(code, {})[name] = url
class Image(dict):
"""Stores image information for a single poster/backdrop (includes
multiple sizes)
"""
def __init__(self, _id, _type, size, url):
self['id'] = _id
self['type'] = _type
def largest(self):
for csize in ["original", "mid", "cover", "thumb"]:
if csize in self:
return csize
def __repr__(self):
return "<Image (%s for ID %s)>" % (self['type'], self['id'])
class ImagesList(list):
"""Stores a list of Images, and functions to filter "only posters" etc"""
def set(self, image_et):
"""Takes an elementtree Element ('image') and stores the url,
along with the type, id and size.
Is a list containing each image as a dictionary (which includes the
various sizes)
For example:
<image type="poster" size="original" url="http://images.themoviedb.org/posters/4181/67926_sin-city-02-color_122_207lo.jpg" id="4181"/>
..becomes:
images[0] = {'id':4181', 'type': 'poster', 'original': 'http://images.themov...'}
"""
_type = image_et.get("type")
_id = image_et.get("id")
size = image_et.get("size")
url = image_et.get("url")
cur = self.find_by('id', _id)
if len(cur) == 0:
nimg = Image(_id = _id, _type = _type, size = size, url = url)
self.append(nimg)
13 years ago
elif len(cur) == 1:
cur[0][size] = url
else:
raise ValueError("Found more than one poster with id %s, this should never happen" % (_id))
def find_by(self, key, value):
ret = []
for cur in self:
if cur[key] == value:
ret.append(cur)
return ret
@property
def posters(self):
return self.find_by('type', 'poster')
@property
def backdrops(self):
return self.find_by('type', 'backdrop')
class CrewRoleList(dict):
"""Stores a list of roles, such as director, actor etc
>>> import tmdb
>>> tmdb.getMovieInfo(550)['cast'].keys()[:5]
['casting', 'producer', 'author', 'sound editor', 'actor']
"""
pass
class CrewList(list):
"""Stores list of crew in specific role
>>> import tmdb
>>> tmdb.getMovieInfo(550)['cast']['author']
[<author (id 7468): Chuck Palahniuk>, <author (id 7469): Jim Uhls>]
"""
pass
class Person(dict):
"""Stores information about a specific member of cast"""
def __init__(self, job, _id, name, character, url):
self['job'] = job
self['id'] = _id
self['name'] = name
self['character'] = character
self['url'] = url
def __repr__(self):
if self['character'] is None or self['character'] == "":
return "<%(job)s (id %(id)s): %(name)s>" % self
else:
return "<%(job)s (id %(id)s): %(name)s (as %(character)s)>" % self
class MovieDb:
"""Main interface to www.themoviedb.com
The search() method searches for the film by title.
The getMovieInfo() method retrieves information about a specific movie using themoviedb id.
"""
def _parseSearchResults(self, movie_element):
cur_movie = MovieResult()
cur_images = ImagesList()
for item in movie_element.getchildren():
if item.tag.lower() == "images":
for subitem in item.getchildren():
cur_images.set(subitem)
else:
cur_movie[item.tag] = item.text
cur_movie['images'] = cur_images
return cur_movie
def _parseMovie(self, movie_element):
cur_movie = Movie()
cur_categories = Categories()
cur_studios = Studios()
cur_countries = Countries()
cur_images = ImagesList()
cur_cast = CrewRoleList()
for item in movie_element.getchildren():
if item.tag.lower() == "categories":
for subitem in item.getchildren():
cur_categories.set(subitem)
elif item.tag.lower() == "studios":
for subitem in item.getchildren():
cur_studios.set(subitem)
elif item.tag.lower() == "countries":
for subitem in item.getchildren():
cur_countries.set(subitem)
elif item.tag.lower() == "images":
for subitem in item.getchildren():
cur_images.set(subitem)
elif item.tag.lower() == "cast":
for subitem in item.getchildren():
job = subitem.get("job").lower()
p = Person(
job = job,
_id = subitem.get("id"),
name = subitem.get("name"),
character = subitem.get("character"),
url = subitem.get("url"),
)
cur_cast.setdefault(job, CrewList()).append(p)
else:
cur_movie[item.tag] = item.text
cur_movie['categories'] = cur_categories
cur_movie['studios'] = cur_studios
cur_movie['countries'] = cur_countries
cur_movie['images'] = cur_images
cur_movie['cast'] = cur_cast
return cur_movie
def search(self, title):
"""Searches for a film by its title.
Returns SearchResults (a list) containing all matches (Movie instances)
"""
title = urllib.quote(title.encode("utf-8"))
13 years ago
url = config['urls']['movie.search'] % (title)
etree = XmlHandler(url).getEt()
search_results = SearchResults()
for cur_result in etree.find("movies").findall("movie"):
cur_movie = self._parseSearchResults(cur_result)
search_results.append(cur_movie)
return search_results
def getMovieInfo(self, id):
"""Returns movie info by it's TheMovieDb ID.
Returns a Movie instance
"""
13 years ago
url = config['urls']['movie.getInfo'] % (id)
etree = XmlHandler(url).getEt()
moviesTree = etree.find("movies").findall("movie")
if len(moviesTree) == 0:
raise TmdNoResults("No results for id %s" % id)
return self._parseMovie(moviesTree[0])
def mediaGetInfo(self, hash, size):
"""Used to retrieve specific information about a movie but instead of
passing a TMDb ID, you pass a file hash and filesize in bytes
"""
13 years ago
url = config['urls']['media.getInfo'] % (hash, size)
etree = XmlHandler(url).getEt()
moviesTree = etree.find("movies").findall("movie")
if len(moviesTree) == 0:
raise TmdNoResults("No results for hash %s" % hash)
return [self._parseMovie(x) for x in moviesTree]
def imdbLookup(self, id = 0, title = False):
13 years ago
if not config.get('apikey'):
raise TmdConfigError("API Key not set")
if id > 0:
13 years ago
url = config['urls']['imdb.lookUp'] % (id)
else:
_imdb_id = self.search(title)[0]["imdb_id"]
13 years ago
url = config['urls']['imdb.lookUp'] % (_imdb_id)
etree = XmlHandler(url).getEt()
lookup_results = SearchResults()
for cur_lookup in etree.find("movies").findall("movie"):
13 years ago
cur_movie = self._parseSearchResults(cur_lookup)
lookup_results.append(cur_movie)
return lookup_results
13 years ago
class Browse:
def __init__(self, params = {}):
"""
tmdb.Browse(params)
default params = {"order_by":"release","order":"desc"}
params = {"query":"some query","release_max":"1991",...}
all posible parameters = http://api.themoviedb.org/2.1/methods/Movie.browse
"""
if "order_by" not in params:
params.update({"order_by":"release"})
if "order" not in params:
params.update({"order":"desc"})
self.params = urllib.urlencode(params)
self.movie = self.look(self.params)
def look(self, look_for):
url = config['urls']['movie.browse'] % (look_for)
etree = XmlHandler(url).getEt()
look_results = SearchResults()
for cur_lookup in etree.find("movies").findall("movie"):
cur_movie = self._parseSearchResults(cur_lookup)
look_results.append(cur_movie)
return look_results
def _parseSearchResults(self, movie_element):
cur_movie = MovieResult()
cur_images = ImagesList()
for item in movie_element.getchildren():
if item.tag.lower() == "images":
for subitem in item.getchildren():
cur_images.set(subitem)
else:
cur_movie[item.tag] = item.text
cur_movie['images'] = cur_images
return cur_movie
def getTotal(self):
return len(self.movie)
def getRating(self, i):
return self.movie[i]["rating"]
def getVotes(self, i):
return self.movie[i]["votes"]
def getName(self, i):
return self.movie[i]["name"]
def getLanguage(self, i):
return self.movie[i]["language"]
def getCertification(self, i):
return self.movie[i]["certification"]
def getUrl(self, i):
return self.movie[i]["url"]
def getOverview(self, i):
return self.movie[i]["overview"]
def getPopularity(self, i):
return self.movie[i]["popularity"]
def getOriginalName(self, i):
return self.movie[i]["original_name"]
def getLastModified(self, i):
return self.movie[i]["last_modified_at"]
def getImdbId(self, i):
return self.movie[i]["imdb_id"]
def getReleased(self, i):
return self.movie[i]["released"]
def getScore(self, i):
return self.movie[i]["score"]
def getAdult(self, i):
return self.movie[i]["adult"]
def getVersion(self, i):
return self.movie[i]["version"]
def getTranslated(self, i):
return self.movie[i]["translated"]
def getType(self, i):
return self.movie[i]["type"]
def getId(self, i):
return self.movie[i]["id"]
def getAlternativeName(self, i):
return self.movie[i]["alternative_name"]
def getPoster(self, i, size):
if size == "thumb" or size == "t":
return self.movie[i]["images"][0]["thumb"]
elif size == "cover" or size == "c":
return self.movie[i]["images"][0]["cover"]
else:
return self.movie[i]["images"][0]["mid"]
def getBackdrop(self, i, size):
if size == "poster" or size == "p":
return self.movie[i]["images"][1]["poster"]
else:
return self.movie[i]["images"][1]["thumb"]
# Shortcuts for tmdb search method
# using:
# movie = tmdb.tmdb("Sin City")
# print movie.getRating -> 7.0
class tmdb:
13 years ago
def __init__(self, name):
"""Convenience wrapper for MovieDb.search - so you can do..
>>> import tmdb
>>> movie = tmdb.tmdb("Fight Club")
>>> ranking = movie.getRanking() or votes = movie.getVotes()
<Search results: [<MovieResult: Fight Club (1999-09-16)>]>
"""
mdb = MovieDb()
13 years ago
self.movie = mdb.search(name)
def getTotal(self):
return len(self.movie)
def getRating(self, i):
return self.movie[i]["rating"]
13 years ago
def getVotes(self, i):
return self.movie[i]["votes"]
13 years ago
def getName(self, i):
return self.movie[i]["name"]
13 years ago
def getLanguage(self, i):
return self.movie[i]["language"]
13 years ago
def getCertification(self, i):
return self.movie[i]["certification"]
13 years ago
def getUrl(self, i):
return self.movie[i]["url"]
13 years ago
def getOverview(self, i):
return self.movie[i]["overview"]
13 years ago
def getPopularity(self, i):
return self.movie[i]["popularity"]
13 years ago
def getOriginalName(self, i):
return self.movie[i]["original_name"]
13 years ago
def getLastModified(self, i):
return self.movie[i]["last_modified_at"]
13 years ago
def getImdbId(self, i):
return self.movie[i]["imdb_id"]
13 years ago
def getReleased(self, i):
return self.movie[i]["released"]
13 years ago
def getScore(self, i):
return self.movie[i]["score"]
13 years ago
def getAdult(self, i):
return self.movie[i]["adult"]
13 years ago
def getVersion(self, i):
return self.movie[i]["version"]
13 years ago
def getTranslated(self, i):
return self.movie[i]["translated"]
13 years ago
def getType(self, i):
return self.movie[i]["type"]
13 years ago
def getId(self, i):
return self.movie[i]["id"]
13 years ago
def getAlternativeName(self, i):
return self.movie[i]["alternative_name"]
13 years ago
def getPoster(self, i, size):
if size == "thumb" or size == "t":
return self.movie[i]["images"][0]["thumb"]
elif size == "cover" or size == "c":
return self.movie[i]["images"][0]["cover"]
else:
return self.movie[i]["images"][0]["mid"]
def getBackdrop(self, i, size):
if size == "poster" or size == "p":
return self.movie[i]["images"][1]["poster"]
else:
return self.movie[i]["images"][1]["thumb"]
# Shortcuts for imdb lookup method
# using:
# movie = tmdb.imdb("Sin City")
13 years ago
# print movie.getRating -> 7.0
class imdb:
13 years ago
def __init__(self, id = 0, title = False):
# get first movie if result=0
"""Convenience wrapper for MovieDb.search - so you can do..
>>> import tmdb
>>> movie = tmdb.imdb(title="Fight Club") # or movie = tmdb.imdb(id=imdb_id)
>>> ranking = movie.getRanking() or votes = movie.getVotes()
<Search results: [<MovieResult: Fight Club (1999-09-16)>]>
"""
self.id = id
self.title = title
self.mdb = MovieDb()
self.movie = self.mdb.imdbLookup(self.id, self.title)
13 years ago
def getTotal(self):
return len(self.movie)
def getRuntime(self, i):
return self.movie[i]["runtime"]
13 years ago
def getCategories(self):
from xml.dom.minidom import parse
13 years ago
adres = config['urls']['imdb.lookUp'] % self.getImdbId()
d = parse(urllib2.urlopen(adres))
s = d.getElementsByTagName("categories")
ds = []
for i in range(len(s[0].childNodes)):
if i % 2 > 0:
ds.append(s[0].childNodes[i].getAttribute("name"))
return ds
13 years ago
def getRating(self, i):
return self.movie[i]["rating"]
13 years ago
def getVotes(self, i):
return self.movie[i]["votes"]
13 years ago
def getName(self, i):
return self.movie[i]["name"]
13 years ago
def getLanguage(self, i):
return self.movie[i]["language"]
13 years ago
def getCertification(self, i):
return self.movie[i]["certification"]
13 years ago
def getUrl(self, i):
return self.movie[i]["url"]
13 years ago
def getOverview(self, i):
return self.movie[i]["overview"]
13 years ago
def getPopularity(self, i):
return self.movie[i]["popularity"]
13 years ago
def getOriginalName(self, i):
return self.movie[i]["original_name"]
13 years ago
def getLastModified(self, i):
return self.movie[i]["last_modified_at"]
13 years ago
def getImdbId(self, i):
return self.movie[i]["imdb_id"]
13 years ago
def getReleased(self, i):
return self.movie[i]["released"]
13 years ago
def getAdult(self, i):
return self.movie[i]["adult"]
13 years ago
def getVersion(self, i):
return self.movie[i]["version"]
13 years ago
def getTranslated(self, i):
return self.movie[i]["translated"]
13 years ago
def getType(self, i):
return self.movie[i]["type"]
13 years ago
def getId(self, i):
return self.movie[i]["id"]
13 years ago
def getAlternativeName(self, i):
return self.movie[i]["alternative_name"]
13 years ago
def getPoster(self, i, size):
poster = []
if size == "thumb" or size == "t":
_size = "thumb"
elif size == "cover" or size == "c":
_size = "cover"
else:
_size = "mid"
for a in self.movie[i]["images"]:
if a["type"] == "poster":
poster.append(a[_size])
return poster
del poster
13 years ago
def getBackdrop(self, i, size):
backdrop = []
if size == "thumb" or size == "t":
_size = "thumb"
elif size == "cover" or size == "c":
_size = "cover"
else:
_size = "mid"
for a in self.movie[i]["images"]:
if a["type"] == "backdrop":
backdrop.append(a[_size])
return backdrop
del backdrop
def imdbLookup(id = 0, title = False):
"""Convenience wrapper for Imdb.Lookup - so you can do..
>>> import tmdb
>>> tmdb.imdbLookup("Fight Club")
<Search results: [<MovieResult: Fight Club (1999-09-16)>]>
"""
mdb = MovieDb()
return mdb.imdbLookup(id, title)
13 years ago
def search(name):
"""Convenience wrapper for MovieDb.search - so you can do..
>>> import tmdb
>>> tmdb.search("Fight Club")
<Search results: [<MovieResult: Fight Club (1999-09-16)>]>
"""
mdb = MovieDb()
return mdb.search(name)
13 years ago
def getMovieInfo(id):
"""Convenience wrapper for MovieDb.search - so you can do..
>>> import tmdb
>>> tmdb.getMovieInfo(187)
<MovieResult: Sin City (2005-04-01)>
"""
mdb = MovieDb()
return mdb.getMovieInfo(id)
def mediaGetInfo(hash, size):
"""Convenience wrapper for MovieDb.mediaGetInfo - so you can do..
>>> import tmdb
>>> tmdb.mediaGetInfo('907172e7fe51ba57', size = 742086656)[0]
<MovieResult: Sin City (2005-04-01)>
"""
mdb = MovieDb()
return mdb.mediaGetInfo(hash, size)
def searchByHashingFile(filename):
"""Searches for the specified file using the OpenSubtitle hashing method
"""
13 years ago
return mediaGetInfo(opensubtitleHashFile(filename), os.path.size(filename))
def main():
results = search("Fight Club")
searchResult = results[0]
movie = getMovieInfo(searchResult['id'])
print movie['name']
print "Producers:"
for prodr in movie['cast']['producer']:
print " " * 4, prodr['name']
print movie['images']
for genreName in movie['categories']['genre']:
print "%s (%s)" % (genreName, movie['categories']['genre'][genreName])
if __name__ == '__main__':
main()