This Python module implements the v3 API for TheMovieDb.org, allowing access
to movie and cast information, as well as related artwork. More information
can be found at:
Access to the API requires a personal key. You can create one by signing up
for an account on TheMovieDb.org, and generating one from your Account Details
page. Once done, the PyTMDB3 module must be be given this key as follows:
>>> from tmdb3 import set_key
>>> set_key('your_api_key')
Caching Engine
In order to limit excessive usage against the online API server, the PyTMDB3
module supports caching of requests. Cached data is keyed off the request URL,
and is currently stored for one hour. API requests are limited to thirty (30)
within ten (10) seconds. Requests beyond this limit are blocking until they
can be processed.
There are currently two engines available for use. The null engine merely
discards all information, and is only intended for debugging use. The file
engine is defualt, and will store to /tmp/pytmdb3.cache unless configured
otherwise. The cache engine can be configured as follows.
>>> from tmdb3 import set_cache
>>> set_cache('null')
>>> set_cache(filename='/full/path/to/cache') # the 'file' engine is assumed
>>> set_cache(filename='tmdb3.cache') # relative paths are put in /tmp
>>> set_cache(engine='file', filename='~/.tmdb3cache')
Locale Configuration
The previous v2 API supported language selection, but would fall through to
the defaults for any fields that did not have language-specific values. The
v3 API no longer performs this fall through, leaving it up to clients to
optionally implement it on their own.
The PyTMDB3 module supports the use of locales in two separate manners. One
can define a global locale that is automatically used if not specified
otherwise, or a specific locale can be supplied directly to searches and
data queries using the locale= keyword argument, which is then propogated
through any subsequent queries made through those objects.
Locale settings are controlled through two functions
set_locale() is used to set the global default locale. It optionally accepts
language and country keyword arguments. If not supplied, it will attempt
to pull such information from your environment. It also accepts a
fallthrough keyword argument, which is used to control the language and
country filter fall through. This is disabled by default, meaning if a
language is set, it will only return information specific to that language.
get_locale() also accepts optional language and country keyword arguments,
and can be used to generate locales to use directly, overriding the global
configuration. If none is given, this instead returns the global
configuration. Note that fall through behavior is applied module-wide, and
individual locales cannot be used to change that behavior.
Authentication
This is not yet supported.
Searching
There are currently six search methods available for use: movies, people,
studios, lists, collections, and series. Search results from TheMovieDb
are sent iteratively, twenty results per page. The search methods provided by
the PyTMDB3 module return list-like structures that will automatically grab
new pages as needed.
>>> from tmdb3 import searchMovie
>>> res = searchMovie('A New Hope')
>>> res
<Search Results: A New Hope>
>>> len(res)
4
>>> res[0]
<Movie 'Star Wars: Episode IV - A New Hope' (1977)>
The movieSearch() method accepts an 'adult' keyword to allow adult content
to be returned. By default, this is set to False and such content is filtered
out. The people search method behaves similarly.
>>> from tmdb import searchPerson
>>> res = searchPerson('Hanks')
>>> res
<Search Results: Hanks>
>>> res[0]
<Person 'Tom Hanks'>
>>> from tmdb import searchStudio
>>> res = searchStudio('Sony Pictures')
>>> res
<Search Results: Sony Pictures>
>>> res[0]
<Studio 'Sony Pictures'>
The movieSearch() method accepts a year keyword, which tells TMDB to
filter for movies of only that specific year. There is a helper method,
movieSearchWithYear(), which will process the release year from movie
names where the year is contained in parentheses, as in:
>>> from tmdb import searchMovieWithYear
>>> list(searchMovieWithYear('Star Wars (1977)'))
[<Movie 'Star Wars: Episode IV - A New Hope' (1977)>, <Movie 'The Making of 'Star Wars'' (1977)>]
Direct Queries
There are currently four data types that support direct access: Collections,
Movies, Persons, and Studios. These each take a single integer ID as an
argument. All data attributes are implemented as properties, and populated
on-demand as used, rather than when the object is created.
>>> from tmdb3 import Collection, Movie, Person, Studio
>>> Collection(10)
<Collection 'Star Wars Collection'>
>>> Movie(11)
<Movie 'Star Wars: Episode IV - A New Hope' (1977)>
>>> Person(2)
<Person 'Mark Hamill'>
>>> Studio(1)
<Studio 'Lucasfilm'>
The Genre class cannot be called by id directly, however it does have a
getAll classmethod, capable of returning all available genres for a specified
language.
Image Behavior
TheMovieDb currently offers three types of artwork: backdrops, posters, and
profiles. The three data queries above will each carry a default one of these
and potentially a list of additionals to choose from. Each can be downloaded
directly, or at one of several pre-scaled reduced resolutions. The PyTMDB3
module provides a list of available sizes, and will generate a URL to download
a requested size. Invalid sizes return an error.
>>> from tmdb3 import Movie
>>> p = Movie(11).poster
>>> p
<Poster 'tvSlBzAdRE29bZe5yYWrJ2ds137.jpg'>
>>> p.sizes()
[u'w92', u'w154', u'w185', u'w342', u'w500', u'original']
>>> p.geturl()
u'http://cf2.imgobject.com/t/p/original/tvSlBzAdRE29bZe5yYWrJ2ds137.jpg'
>>> p.geturl('w342')
u'http://cf2.imgobject.com/t/p/w342/tvSlBzAdRE29bZe5yYWrJ2ds137.jpg'
>>> p.geturl('w300')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "tmdb3/tmdb_api.py", line 101, in geturl
raise TMDBImageSizeError
tmdb3.tmdb_exceptions.TMDBImageSizeError: None
Trailers
TheMovieDb offers access to trailers on Youtube and Apple, however their use
is slightly different. Youtube trailers offer an individual file, while Apple
trailers offer multiple sizes.
for m in data:
File "/usr/local/lib/python2.7/dist-packages/tmdb3/pager.py", line 25, in next
return self._parent[self._index]
File "/usr/local/lib/python2.7/dist-packages/tmdb3/pager.py", line 70, in __getitem__
return self._data[index]
IndexError: list index out of range
The Datapoint definition allows a handler callable to be supplied, for which the data returned by queries is passed through. Due to the manner datapoints are defined, and Python's lack of prototypes, this handler must be fully defined in the namespace at the point the Datapoint is created. This leads to circular dependency issues where two classes must be defined before each other, such as the Studio class having a 'parent' attribute that back-references to itself during its own creation. Right now, this is worked around by using a lambda function, however that prevents the locale passthrough from happening properly.
The second time the search results are printed "The Tenant (1976)" becomes "Le locataire (1976)". When any of Runtime, IMDB, or Overview are printed this happens. If just the Title and/or Originaltitle is printed by itself, it doesn't.
tmdb3.set_key('99999999999999999999999999999')
Traceback (most recent call last):
File "", line 1, in
File "tmdb3/request.py", line 33, in set_key
raise TMDBKeyInvalid("Specified API key must be 128-bit hex")
tmdb3.tmdb_exceptions.TMDBKeyInvalid: Specified API key must be 128-bit hex
The key being supplied is identical to the key listed in the web site. It also being accepted by the old tmdb api without issue.
Looks like a pip install now causes a crash due to README.md not being included in the package.
Downloading/unpacking tmdb3 (from FlexGet==1.2.265.dev)
Downloading tmdb3-0.7.2.tar.gz
Running setup.py (path:/home/travis/virtualenv/python2.6.9/build/tmdb3/setup.py) egg_info for package tmdb3
Traceback (most recent call last):
File "<string>", line 17, in <module>
File "/home/travis/virtualenv/python2.6.9/build/tmdb3/setup.py", line 9, in <module>
with open('README.md') as f:
IOError: [Errno 2] No such file or directory: 'README.md'
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "<string>", line 17, in <module>
File "/home/travis/virtualenv/python2.6.9/build/tmdb3/setup.py", line 9, in <module>
with open('README.md') as f:
IOError: [Errno 2] No such file or directory: 'README.md'
I can't seem to find anything in the API that lets me specify a location I'm interested in when searching. A movie will be released at a different time depending on, say, a lat-long tuple. The "releasedate" field seems to always be indicating the US release date? Is there a way I can get the release date of a movie for, say, UK or Brazil?
The Person, Studio, Movie, and Collection queries all return some form of image, but only provide the filename. There is no mechanism to query additional information for those images, so attempts to access those datapoints will result in an error. For images coming from movies and people, this information can be accessed through other queries available to the parent, so some method could be devices to properly populate those objects.
"But, I think I might be onto something ... turns out that when there's only one movie with the name (I assume it parses the filename and treats it like a search term), then it doesn't return anything, but if there's only one name, then it picks up the metadata just fine. If that's the case, I read that there should be a listing of found files and you can select the one you want. "
Since the TMDB API returns a 404 status error when looking up an ID that doesn't exist, the urllib2.urlopen() call in open() in request.py throws a HTTPError Exception, when it should throw a TMDBRequestError (Invalid id - The pre-requisite id is invalid or not found) if it correctly parses the JSON response.
Therefore, it seems that we cannot correctly distinguish between a connection error to TMDB and a normal 'not found' error.
I have fixed this locally using the following work-around, but I don't really like it:
In request.py/open(), the exception handling should be:
excepturllib2.HTTPError, e:
if (e.code==404):
returne.fpraiseTMDBHTTPError(str(e))
By forcing these errors to be parsed, the JSON will be read even if we got a 404 from the server. It seems safe as the API should always return a valid JSON response.
The day of birth is not necessarily formatted as "YYYY-MM-DD". http://www.themoviedb.org/person/10295, for instance, contains only the year which causes process_date() to fail.
Python 2.7.3rc2 (default, Apr 22 2012, 22:30:17)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import tmdb3
>>> tmdb3.set_key('7243d57358963cb7388deb6d1b0f50f5')
>>> person = tmdb3.Person(10295)
>>> print person.name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "tmdb3/util.py", line 143, in __get__
self.poller.__get__(inst, owner)()
File "tmdb3/util.py", line 77, in __call__
self.apply(req.readJSON())
File "tmdb3/util.py", line 86, in apply
setattr(self.inst, v, data[k])
File "tmdb3/util.py", line 148, in __set__
value = self.handler(value)
File "tmdb3/util.py", line 163, in <lambda>
self.handler = lambda x: handler(x)
File "tmdb3/tmdb_api.py", line 67, in process_date
return datetime.date(*[int(x) for x in datestr.split('-')])
TypeError: Required argument 'month' (pos 2) not found
>>>
I am using PyTMDB3 and for the most part successfully, although I have 1 problem. I have the following code:
import os
from tmdb3 import searchMovie
from tmdb3 import set_key
set_key('<APIKEY>')
for path, subdirs, files in os.walk(r'/media/Movies'):
files = [ file for file in files if not file.endswith( ('.nfo','.tbn','.jpg','.xml','.srt') ) ]
for filename in files:
fname = str(os.path.splitext(filename)[0])
res = searchMovie(fname)
print (res[0])
It goes through my movies and prints out the movie name found on tmdb. It works although there are 5 movies in which it doesnt work and I get an error and I cant figure out why.
The movies are Madagascar, Robin Hood, The Grey, The Watch and Zombieland. I can see that they are all on tmdb so am not sure why it isnt finding them.
The error is:
Traceback (most recent call last):
File "scan.py", line 14, in
res = searchMovie(fname)
File "/usr/local/lib/python2.7/dist-packages/tmdb3/tmdb_api.py", line 121, in searchMovie
return MovieSearchResult(Request('search/movie', **kwargs), locale=locale)
File "/usr/local/lib/python2.7/dist-packages/tmdb3/tmdb_api.py", line 148, in init lambda x: Movie(raw=x, locale=locale))
File "/usr/local/lib/python2.7/dist-packages/tmdb3/pager.py", line 101, in init super(PagedRequest, self).init(self._getpage(1), 20)
File "/usr/local/lib/python2.7/dist-packages/tmdb3/pager.py", line 56, in __init__self._data = list(iterable)
File "/usr/local/lib/python2.7/dist-packages/tmdb3/pager.py", line 108, in _ge tpageyield self._handler(item)
File "/usr/local/lib/python2.7/dist-packages/tmdb3/tmdb_api.py", line 148, in lambda x: Movie(raw=x, locale=locale))
File "/usr/local/lib/python2.7/dist-packages/tmdb3/util.py", line 350, in __call__obj._populate.apply(kwargs['raw'], False)
File "/usr/local/lib/python2.7/dist-packages/tmdb3/util.py", line 83, in apply if (k in
TypeError: argument of type 'NoneType' is not iterable
Since so many people upload backdrops without a language, the backdrops list is almost always empty when specifying a language. I think backdrops list should contain both types of backdrops or at least have an option for it. Right now, using a language effectively breaks backdrop support.
looping through the response of "Gravity" will throw an "list index out of range" exception. This also happens with movies like "Imagine" or "Mamma Mia!". I am sure about the cause of this problem.
I am looking forward to your answer and appreciate your help
Ben
code example:
response = searchMovie("Gravity")
print "length", len(response)
for x in xrange(0, len(response)):
print "%d title: %s" % (x, response[x].title)
Output:
length 16
0 title: Gravity
1 title: Gravity
2 title: Gravity
3 title: Nazi Ufos: How They Fly - Exposing the German Tesla Anti-Gravity & Free Energy Program
4 title: Defying Gravity
5 title: Beyond Gravity
6 title: Defying Gravity
7 title: Laws of Gravity
8 title: Anti Gravity Unhinged
9 title: The Division of Gravity
10 title: Gravity Is My Enemy
11 title: Gravity was everywhere back then
12 title: What on Earth is Wrong with Gravity
13 title: Love and Debate
14 title: Way of Life
Traceback (most recent call last):
File "exampleError.py", line 24, in
print "%d title: %s" % (x, response[x].title)
File "/Library/Python/2.7/site-packages/tmdb3/pager.py", line 70, in getitem
return self._data[index]
IndexError: list index out of range
Hi, I am working on a project that must communicate with themoviedb to retrieve some data and would like to use pytmdb3. The problem is the project I'm working on is in python 3. Do you plan on porting the code to py3k? Any idea if such task would be too hard? It would be great not to start from scratch :)
When name searching with: nausicaä of the valley of the wind 1984
You get teh follwing error:
tmdb3/request.py", line 58, in init
urllib.urlencode(kwargs))
File "/usr/lib/python2.7/urllib.py", line 1311, in urlencode
v = quote_plus(str(v))
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 7: ordinal not in range(128)
I'm considering to use this API wrapper for a small application but I did not find any information about its licence.
Could you please tell me under what licence it is released ?
independently of the locale set, the primary release date of a specific movie gets returned.
This behavior can be seen if you set the locale to 'DE' and fetch "Wir sind die Millers" (original title: We're the Millers). The returned release date is always "2013-08-07" which is the primary one. It is supposed to be "2013-08-28" though.
I am looking forward to your answer and appreciate your help
Ben
code example:
set_locale('de', 'DE')
response = searchMovie("Wir sind die Millers")
for movie in response:
print movie.releasedate //will return 2013-08-07
Clarify if this library is the same as the "tmdb3" available here
Basic installation procedure (pip, or clone repo or other).
e.g. if (1) is true can I pip install tmdb3
Maybe link to the corresponding Pypi page and author homepage?
I know the above may seem obvious but I reckon it'd help the less experienced among us. I've worked it out myself but now but I reckon having them up front and centre would be good for others.
Anyway - Fantastic library and very much appreciate your work!! :)
now that the caching is configurable, it works great for the first nothing in cache but the second hit (even if it's the same title) errors. this is with and without using the cache configuration..
I also couldn't easily configure the cache until I added it to the import in tmdb_api.py mimicing how you were using set_api
I apologize for not having the exact errors, I forgot to post this before leaving the house.
A traceback from our software when looking up a movie which has an alternate title with a non-ascii country name. The code calls str(country) which will fail for any non-ascii country name. If country codes are meant to be ascii only, the library should throw out the invalid result. If not, it should always be dealing with the result as unicode, and never as bytes.
The movie causing this traceback is Rocky (tt0075148).
Traceback (most recent call last):
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\task.py", line 420, in __run_plugin
return method(*args, **kwargs)
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\event.py", line 21, in __call__
return self.func(*args, **kwargs)
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\plugins\output\dump.py", line 83, in on_task_output
dump(undecided, task.options.debug, eval_lazy, trace)
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\plugins\output\dump.py", line 35, in dump
value = entry[field]
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\entry.py", line 270, in __getitem__
return result()
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\entry.py", line 43, in __call__
result = func(self.entry, self.field)
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\plugins\metainfo\tmdb_lookup.py", line 59, in lazy_loader
imdb_id=imdb_id)
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\utils\database.py", line 25, in wrapper
result = func(*args, **kwargs)
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\plugins\api_tmdb.py", line 289, in lookup
ApiTmdb.get_movie_details(movie, session)
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\plugins\api_tmdb.py", line 331, in get_movie_details
movie.update_from_object(result)
File "C:\Users\chase.sterling\PycharmProjects\Flexget\flexget\plugins\api_tmdb.py", line 121, in update_from_object
if len(update_object.alternate_titles) > 0:
File "C:\Users\chase.sterling\PycharmProjects\Flexget\lib\site-packages\tmdb3\util.py", line 152, in __get__
self.poller.__get__(inst, owner)()
File "C:\Users\chase.sterling\PycharmProjects\Flexget\lib\site-packages\tmdb3\util.py", line 80, in __call__
self.apply(req.readJSON())
File "C:\Users\chase.sterling\PycharmProjects\Flexget\lib\site-packages\tmdb3\util.py", line 89, in apply
setattr(self.inst, v, data[k])
File "C:\Users\chase.sterling\PycharmProjects\Flexget\lib\site-packages\tmdb3\util.py", line 223, in __set__
data.sort()
File "C:\Users\chase.sterling\PycharmProjects\Flexget\lib\site-packages\tmdb3\tmdb_api.py", line 282, in __lt__
return (self.country == self._locale.country) \
File "C:\Users\chase.sterling\PycharmProjects\Flexget\lib\site-packages\tmdb3\locales.py", line 44, in __eq__
return (id(self) == id(other)) or (str(self) == str(other))
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)
when querying information about cast or crew I stumbled over some strange behavior:
If I access the Cast.dayofbirth field first, a query to Cast.order or Cast.character will return an empty string. Other fields like name are still accessible and will return correct values.
If I turn around the access order and query Cast.dayofbirth last everything works as expected.
I saw the same behavior when working with the crew objects. It seems that fields from the Cast or Crew classes get lost after an access to dayofbirth. The fields of the parent class Person remain intact.
Python 2.7.3 (default, Apr 24 2012, 00:00:54)
[GCC 4.7.0 20120414 (prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import tmdb3
>>> tmdb3.set_key('...')
>>> movie = tmdb3.Movie.fromIMDB('tt1001526')
>>> print movie.cast[0].dayofbirth
1967-07-16
>>> print movie.cast[0].character
>>> print movie.cast[0]
<Cast 'Will Ferrell' as ''>
>>> print movie.cast[0].order
>>> print movie.cast[0].name
Will Ferrell
>>> movie = tmdb3.Movie.fromIMDB('tt1001526')
>>> print movie.cast[0].character
Megamind (voice)
>>> print movie.cast[0].dayofbirth
1967-07-16
>>>
I need the new TV related stuff and since pypi reports 0.7.0 as the latest tmdb3 version (https://pypi.python.org/pypi/tmdb3) I'm trying to upgrade my python setup, but both pip and easy_install, with --upgrade option, are stuck on the 0.6.17. they say it's the latest version available. is it only happening to me?
from tmdb3 import searchMovie
Traceback (most recent call last):
File "<pyshell#4>", line 1, in
from tmdb3 import searchMovie
File "C:\Python34\lib\site-packages\tmdb3__init__.py", line 3, in
from tmdb_api import Configuration, searchMovie, searchMovieWithYear,
ImportError: No module named 'tmdb_api'
Request Rate Limiting
We do enforce a small amount of rate limiting. Please be aware that should you exceed these limits, you will receive a 503 error.
30 requests every 10 seconds per IP
Maximum 20 simultaneous connections per IP
Given that it's so simple to use tmdb3 and that it makes requests to TMDB pretty much seamlessly just by accessing properties on an object, is there a way to limit the rate of requests sent to the TMDB backends?
Here's a code example to illustrate:
people = tmdb3.searchPerson("brad pitt") # calls backend
for person in people:
for role in person.roles: # calls backend
print role.releases # calls backend
Here the 30QPS is impossible to enforce on the client side. The cache may help reduce the number of queries but the problem is still there.
It seems the freebsd package is being selected by pip on non-freebsd systems and causing install errors. Downstream ticket: http://flexget.com/ticket/2788
movie2.poster.language
Traceback (most recent call last):
File "", line 1, in
File "/usr/local/lib/python2.7/dist-packages/tmdb3/util.py", line 143, in get
self.poller.get(inst, owner)()
File "/usr/local/lib/python2.7/dist-packages/tmdb3/util.py", line 61, in call
raise RuntimeError('Poller object called without a source function')
RuntimeError: Poller object called without a source function
Perhaps I'm misunderstanding how this is supposed to work, but it looks like all request parameters are encoded using the system locale encoding. (https://github.com/wagnerrp/pytmdb3/blob/master/tmdb3/request.py#L70) This causes problems when the system locale cannot encode all the charaters in the parameters, plus, I have no idea how tmdb is expected to know what encoding you have used to encode the parameters, I suspect it should be using a constant encoding defined by the tmdb api.
Portion of a relevant traceback:
File "/usr/local/lib/python2.7/dist-packages/flexget/plugins/api_tmdb.py", line 293, in lookup
result = _first_result(tmdb3.tmdb_api.searchMovie(title.lower(), adult=True, year=year))
File "/usr/local/lib/python2.7/dist-packages/tmdb3/tmdb_api.py", line 128, in searchMovie
return MovieSearchResult(Request('search/movie', **kwargs), locale=locale)
File "/usr/local/lib/python2.7/dist-packages/tmdb3/request.py", line 71, in __init__
kwargs[k] = locale.encode(v)
File "/usr/local/lib/python2.7/dist-packages/tmdb3/locales.py", line 110, in encode
return dat.encode(self.encoding)
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 0-13: ordinal not in range(256)