#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# ----------------------
# Name: tmdb_api.py      Simple-to-use Python interface to The TMDB's API (www.themoviedb.org)
# Python Script
# Author:   dbr/Ben modified by R.D. Vaughan
# Purpose:  This python script is intended to perform a variety of utility functions to search and access text
#           metadata and image URLs from TMDB. These routines are based on the v2.1 TMDB api. Specifications
#           for this apu are published at http://api.themoviedb.org/2.1/
#
# License:Creative Commons GNU GPL v2
# (http://creativecommons.org/licenses/GPL/2.0/)
#-------------------------------------
__title__ ="tmdb_api - Simple-to-use Python interface to The TMDB's API (www.themoviedb.org)";
__author__="dbr/Ben modified by R.D. Vaughan"
__purpose__='''
This python script is intended to perform a variety of utility functions to search and access text
metadata and image URLs from TMDB. These routines are based on the v2.1 TMDB api. Specifications
for this api are published at http://api.themoviedb.org/2.1/
'''

__version__="v0.1.8"
# 0.1.0 Initial development
# 0.1.1 Alpha Release
# 0.1.2 Added removal of any line-feeds from data
# 0.1.3 Added display of URL to TMDB XML when debug was specified
#       Added check and skipping any empty data from TMDB
# 0.1.4 More data validation added (e.g. valid image file extentions)
#       More data massaging added.
# 0.1.5 Added a superclass to perform TMDB Trailer searches for the Mythnetvison grabber tmdb_nv.py
# 0.1.6 Improved displayed error messages on an exception abort
# 0.1.7 Fixed issues with interactive movie selection
# 0.1.8 Fixed the error message reporting when the machines Internet connection or DNS is not working

import os, struct, sys, time
import urllib, urllib2
import logging

from tmdb_ui import BaseUI, ConsoleUI
from tmdb_exceptions import (TmdBaseError, TmdHttpError, TmdXmlError, TmdbUiAbort, TmdbMovieOrPersonNotFound,)


class OutStreamEncoder(object):
    """Wraps a stream with an encoder"""
    def __init__(self, outstream, encoding=None):
        self.out = outstream
        if not encoding:
            self.encoding = sys.getfilesystemencoding()
        else:
            self.encoding = encoding

    def write(self, obj):
        """Wraps the output stream, encoding Unicode strings with the specified encoding"""
        if isinstance(obj, unicode):
            try:
                self.out.write(obj.encode(self.encoding))
            except IOError:
                pass
        else:
            try:
                self.out.write(obj)
            except IOError:
                pass

    def __getattr__(self, attr):
        """Delegate everything but write to the stream"""
        return getattr(self.out, attr)
sys.stdout = OutStreamEncoder(sys.stdout, 'utf8')
sys.stderr = OutStreamEncoder(sys.stderr, 'utf8')


try:
    import xml.etree.cElementTree as ElementTree
except ImportError:
    import xml.etree.ElementTree as ElementTree


class XmlHandler:
    """Deals with retrieval of XML files from API
    """
    def __init__(self, url):
        self.url = url

    def _grabUrl(self, url):
        try:
            urlhandle = urllib.urlopen(url)
        except IOError, errormsg:
            raise TmdHttpError(errormsg)
        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 u"<Search results: %s>" % (list.__repr__(self))


class Movie(dict):
    """A dict containing the information about the film
    """
    def __repr__(self):
        return u"<Movie: %s>" % self.get(u"title")


class MovieAttribute(dict):
    """Base class for more complex attributes (like Poster,
    which has multiple resolutions)
    """
    pass


class Poster(MovieAttribute):
    """Stores poster image URLs, each size is under the appropriate dict key.
    Common sizes are: cover, mid, original, thumb
    """
    def __repr__(self):
        return u"<%s with sizes %s>" % (
            self.__class__.__name__,
            u", ".join(
                [u"'%s'" % x for x in sorted(self.keys())]
            )
        )

    def set(self, poster_et):
        """Takes an elementtree Element ('poster') and appends the poster,
        with a the size as the dict key.

        For example:
        <image type="poster"
            size="cover"
            url="http://example.org/poster_original.jpg"
            id="36431"
        />

        ..becomes:
        poster['cover'] = ['http://example.org/poster_original.jpg, '36431']
        """
        size = poster_et.get(u"size").strip()
        url = poster_et.get(u"url").strip()
        (dirName, fileName) = os.path.split(url)
        (fileBaseName, fileExtension)=os.path.splitext(fileName)
        if not fileExtension[1:].lower() in self.image_extentions:
            return
        imageid = poster_et.get(u"id").strip()
        if not self.has_key(size):
            self[size] = [[url, imageid]]
        else:
            self[size].append([url, imageid])

    def largest(self):
        """Attempts to return largest image.
        """
        for cur_size in [u"original", u"mid", u"cover", u"thumb"]:
            if cur_size in self:
                return self[cur_size]

    def medium(self):
        """Attempts to return medium size image.
        """
        for cur_size in [u"cover", u"thumb", u"mid", u"original", ]:
            if cur_size in self:
                return self[cur_size]

class Backdrop(Poster):
    """Stores backdrop image URLs, each size under the appropriate dict key.
    Common sizes are: mid, original, thumb
    """
    pass

class People(MovieAttribute):
    """Stores people in a dictionary of roles in the movie.
    """
    def __repr__(self):
        return u"<%s with jobs %s>" % (
            self.__class__.__name__,
            u", ".join(
                [u"'%s'" % x for x in sorted(self.keys())]
            )
        )

    def set(self, cast_et):
        """Takes an element tree Element ('cast') and stores a dictionary of roles,
        for each person.

        For example:
        <cast>
            <person
                url="http://www.themoviedb.org/person/138"
                name="Quentin Tarantino" job="Director"
                character="Special Guest Director"
                id="138"/>
            ...
         </cast>

        ..becomes:
        self['people']['director'] = 'Robert Rodriguez'
        """
        self[u'people']={}
        people = self[u'people']
        for node in cast_et.getchildren():
            if node.get(u'name') != None:
                try:
                    key = unicode(node.get(u"job").lower(), 'utf8')
                except (UnicodeEncodeError, TypeError):
                    key = node.get(u"job").lower().strip()
                try:
                    data = unicode(node.get(u'name'), 'utf8')
                except (UnicodeEncodeError, TypeError):
                    data = node.get(u'name')
                if people.has_key(key):
                    people[key]=u"%s,%s" % (people[key], data.strip())
                else:
                    people[key]=data.strip()

class MovieDb(object):
    """Main interface to www.themoviedb.org

    Supports several search TMDB search methods and a number of TMDB data retrieval methods.
    The apikey is a maditory parameter when creating an instance of this class
    """
    def __init__(self,
                apikey,
                mythtv = False,
                interactive = False,
                select_first = False,
                debug = False,
                custom_ui = None,
                language = None,
                search_all_languages = False, ###CHANGE - Needs to be added
                ):
        """apikey (str/unicode):
            Specify the themoviedb.org API key. Applications need their own key.
            See http://api.themoviedb.org/2.1/ to get your own API key

        mythtv (True/False):
            When True, the movie metadata is being returned has the key and values massaged to match MythTV
            When False, the movie metadata is being returned matches what TMDB returned

        interactive (True/False):
            When True, uses built-in console UI is used to select the correct show.
            When False, the first search result is used.

        select_first (True/False):
            Automatically selects the first series search result (rather
            than showing the user a list of more than one series).
            Is overridden by interactive = False, or specifying a custom_ui

        debug (True/False):
             shows verbose debugging information

        custom_ui (tmdb_ui.BaseUI subclass):
            A callable subclass of tmdb_ui.BaseUI (overrides interactive option)

        language (2 character language abbreviation):
            The language of the returned data. Is also the language search
            uses. Default is "en" (English). For full list, run..

            >>> MovieDb().config['valid_languages'] #doctest: +ELLIPSIS
            ['da', 'fi', 'nl', ...]

        search_all_languages (True/False):
            By default, TMDB will only search in the language specified using
            the language option. When this is True, it will search for the
            show in any language

        """
        self.config = {}

        if apikey is not None:
            self.config['apikey'] = apikey
        else:
            sys.stderr.write("\n! Error: An TMDB API key must be specified. See http://api.themoviedb.org/2.1/ to get your own API key\n\n")
            sys.exit(1)

        # Set the movie details function to either massaged for MythTV or left as it is returned by TMDB
        if mythtv:
            self.movieDetails = self._mythtvDetails
        else:
            self.movieDetails = self._tmdbDetails

        self.config['debug_enabled'] = debug # show debugging messages

        self.log = self._initLogger() # Setups the logger (self.log.debug() etc)

        self.config['custom_ui'] = custom_ui

        self.config['interactive'] = interactive # prompt for correct series?

        self.config['select_first'] = select_first

        self.config['search_all_languages'] = search_all_languages

        # The supported languages have not been published or enabled at this time.
        # List of language from ???????
        # Hard-coded here as it is realtively static, and saves another HTTP request, as
        # recommended on ?????
        #self.config['valid_languages'] = [
        #    "da", "fi", "nl", "de", "it", "es", "fr","pl", "hu","el","tr",
        #    "ru","he","ja","pt","zh","cs","sl", "hr","ko","en","sv","no"
        #]

        # ONLY ENGISH is supported at this time
        self.config['language'] = "en"
        #if language is None:
        #    self.config['language'] = "en"
        #elif language not in self.config['valid_languages']:
        #    raise ValueError("Invalid language %s, options are: %s" % (
        #        language, self.config['valid_languages']
        #    ))
        #else:
        #    self.config['language'] = language

        # The following url_ configs are based of the
        # http://api.themoviedb.org/2.1/
        self.config['base_url'] = "http://api.themoviedb.org/2.1"

        self.lang_url = u'/%s/xml/' # Used incase the user wants to overside the configured/default language

        self.config[u'urls'] = {}

        # v2.1 api calls
        self.config[u'urls'][u'movie.search'] = u'%(base_url)s/Movie.search/%(language)s/xml/%(apikey)s/%%s' % (self.config)
        self.config[u'urls'][u'tmdbid.search'] = u'%(base_url)s/Movie.getInfo/%(language)s/xml/%(apikey)s/%%s' % (self.config)
        self.config[u'urls'][u'imdb.search'] = u'%(base_url)s/Movie.imdbLookup/%(language)s/xml/%(apikey)s/tt%%s'  % (self.config)
        self.config[u'urls'][u'image.search'] = u'%(base_url)s/Movie.getImages/%(language)s/xml/%(apikey)s/%%s' % (self.config)
        self.config[u'urls'][u'person.search'] = u'%(base_url)s/Person.search/%(language)s/xml/%(apikey)s/%%s' % (self.config)
        self.config[u'urls'][u'person.info'] = u'%(base_url)s/Person.getInfo/%(language)s/xml/%(apikey)s/%%s' % (self.config)
        self.config[u'urls'][u'hash.info'] = u'%(base_url)s/Hash.getInfo/%(language)s/xml/%(apikey)s/%%s' % (self.config)

        # Translation of TMDB elements into MythTV keys/db grabber names
        self.config[u'mythtv_translation'] = {u'actor': u'cast', u'backdrop': u'fanart', u'categories': u'genres', u'director':  u'director', u'id': u'inetref', u'name': u'title', u'overview': u'plot', u'rating': u'userrating', u'poster': u'coverart', u'production_countries': u'countries', u'released': u'releasedate', u'runtime': u'runtime', u'url': u'url', u'imdb_id': u'imdb', u'certification': u'movierating', }
        self.config[u'image_extentions'] = ["png", "jpg", "bmp"] # Acceptable image extentions
        self.thumbnails = False
    # end __init__()


    def _initLogger(self):
        """Setups a logger using the logging module, returns a log object
        """
        logger = logging.getLogger("tmdb")
        formatter = logging.Formatter('%(asctime)s) %(levelname)s %(message)s')

        hdlr = logging.StreamHandler(sys.stdout)

        hdlr.setFormatter(formatter)
        logger.addHandler(hdlr)

        if self.config['debug_enabled']:
            logger.setLevel(logging.DEBUG)
        else:
            logger.setLevel(logging.WARNING)
        return logger
    #end initLogger


    def textUtf8(self, text):
        if text == None:
            return text
        try:
            return unicode(text, 'utf8')
        except (UnicodeEncodeError, TypeError):
            return text
    # end textUtf8()


    def getCategories(self, categories_et):
        """Takes an element tree Element ('categories') and create a string of comma seperated film
        categories
        return comma seperated sting of film category names

        For example:
        <categories>
            <category type="genre" url="http://themoviedb.org/encyclopedia/category/80" name="Crime"/>
            <category type="genre" url="http://themoviedb.org/encyclopedia/category/18" name="Drama"/>
            <category type="genre" url="http://themoviedb.org/encyclopedia/category/53" name="Thriller"/>
        </categories>

        ..becomes:
        'Crime,Drama,Thriller'
        """
        cat = u''
        for category in categories_et.getchildren():
            if category.get(u'name') != None:
                cat+=u"%s," % self.textUtf8(category.get(u'name').strip())
        if len(cat):
            cat=cat[:-1]
        return cat


    def getStudios(self, studios_et):
        """Takes an element tree Element ('studios') and create a string of comma seperated film
        studios names
        return comma seperated sting of film studios names

        For example:
        <studios>
            <studio url="http://www.themoviedb.org/encyclopedia/company/20" name="Miramax Films"/>
            <studio url="http://www.themoviedb.org/encyclopedia/company/334" name="Dimension"/>
        </studios>

        ..becomes:
        'Miramax Films,Dimension'
        """
        cat = u''
        for studio in studios_et.getchildren():
            if studio.get(u'name') != None:
                cat+=u"%s," % self.textUtf8(studio.get(u'name').strip())
        if len(cat):
            cat=cat[:-1]
        return cat

    def getProductionCountries(self, countries_et):
        """Takes an element tree Element ('countries') and create a string of comma seperated film
        countries
        return comma seperated sting of countries associated with the film

        For example:
        <countries>
            <country url="http://www.themoviedb.org/encyclopedia/country/223" name="United States of America" code="US"/>
        </countries>
        ..becomes:
        'United States of America'
        """
        countries = u''
        for country in countries_et.getchildren():
            if country.get(u'name') != None:
                if len(countries):
                    countries+=u", %s" % self.textUtf8(country.get(u'name').strip())
                else:
                    countries=self.textUtf8(country.get(u'name').strip())
        return countries


    def _tmdbDetails(self, movie_element):
        '''Create a dictionary of movie details including text metadata, image URLs (poster/fanart)
        return the dictionary of movie information
        '''
        cur_movie = Movie()
        cur_poster = Poster()
        cur_backdrop = Backdrop()
        cur_poster.image_extentions = self.config[u'image_extentions']
        cur_backdrop.image_extentions = self.config[u'image_extentions']
        cur_people = People()

        for item in movie_element.getchildren():
            if item.tag.lower() == u"images":
                for image in item.getchildren():
                    if image.get(u"type").lower() == u"poster":
                        cur_poster.set(image)
                    elif image.get(u"type").lower() == u"backdrop":
                        cur_backdrop.set(image)
            elif item.tag.lower() == u"categories":
                cur_movie[u'categories'] = self.getCategories(item)
            elif item.tag.lower() == u"studios":
                cur_movie[u'studios'] = self.getStudios(item)
            elif item.tag.lower() == u"countries":
                cur_movie[u'production_countries'] = self.getProductionCountries(item)
            elif item.tag.lower() == u"cast":
                cur_people.set(item)
            else:
                if item.text != None:
                    tag = self.textUtf8(item.tag.strip())
                    cur_movie[tag] = self.textUtf8(item.text.strip())

        if cur_poster.largest() != None:
            tmp = u''
            for imagedata in cur_poster.largest():
                if imagedata[0]:
                    tmp+=u"%s," % imagedata[0]
            if len(tmp):
                tmp = tmp[:-1]
                cur_movie[u'poster'] = tmp
        if self.thumbnails and cur_poster.medium() != None:
            tmp = u''
            for imagedata in cur_poster.medium():
                if imagedata[0]:
                    tmp+=u"%s," % imagedata[0]
            if len(tmp):
                tmp = tmp[:-1]
                cur_movie[self.thumbnails] = tmp
        if cur_backdrop.largest() != None:
            tmp = u''
            for imagedata in cur_backdrop.largest():
                if imagedata[0]:
                    tmp+=u"%s," % imagedata[0]
            if len(tmp):
                tmp = tmp[:-1]
                cur_movie[u'backdrop'] = tmp
        if cur_people.has_key(u'people'):
            if cur_people[u'people'] != None:
                for key in cur_people[u'people']:
                    if cur_people[u'people'][key]:
                        cur_movie[key] = cur_people[u'people'][key]

        if self._tmdbDetails == self.movieDetails:
            data = {}
            for key in cur_movie.keys():
                if cur_movie[key]:
                    data[key] = cur_movie[key]
            return data
        else:
            return cur_movie
    # end _tmdbDetails()


    def _mythtvDetails(self, movie_element):
        '''Massage the movie details into key value pairs as compatible with MythTV
        return a dictionary of massaged movie details
        '''
        if movie_element == None:
            return {}
        cur_movie = self._tmdbDetails(movie_element)
        translated={}
        for key in cur_movie:
            if cur_movie[key] == None or cur_movie[key] == u'None':
                continue
            if isinstance(cur_movie[key], str) or isinstance(cur_movie[key], unicode):
                if cur_movie[key].strip() == u'':
                    continue
                else:
                   cur_movie[key] = cur_movie[key].strip()
            if key in [u'rating']:
                if cur_movie[key] == 0.0 or cur_movie[key] == u'0.0':
                    continue
            if key in [u'popularity', u'budget', u'runtime', u'revenue', ]:
                if cur_movie[key] == 0 or cur_movie[key] == u'0':
                    continue
            if key == u'imdb_id':
                cur_movie[key] = cur_movie[key][2:]
            if key == u'released':
                translated[u'year'] = cur_movie[key][:4]
            if self.config[u'mythtv_translation'].has_key(key):
                translated[self.config[u'mythtv_translation'][key]] = cur_movie[key]
            else:
                translated[key] = cur_movie[key]
            for key in translated.keys():
                if translated[key]:
                    translated[key] = translated[key].replace(u'\n',u' ') # Remove any line-feeds from data
        return translated
    # end _mythtvDetails()


    def searchTitle(self, title, lang=False):
        """Searches for a film by its title.
        Returns SearchResults (a list) containing all matches (Movie instances)
        """
        if lang: # Override language
            URL = self.config[u'urls'][u'movie.search'].replace(self.lang_url % self.config['language'], self.lang_url % lang)
        else:
            URL = self.config[u'urls'][u'movie.search']
        org_title = title
        title = urllib.quote(title.encode("utf-8"))
        url = URL % (title)
        if self.config['debug_enabled']:        # URL so that raw TMDB XML data can be viewed in a browser
            sys.stderr.write(u'\nDEBUG: XML URL:%s\n\n' % url)

        etree = XmlHandler(url).getEt()
        if etree is None:
            raise TmdbMovieOrPersonNotFound(u'No Movies matching the title (%s)' % org_title)

        search_results = SearchResults()
        for cur_result in etree.find(u"movies").findall(u"movie"):
            if cur_result == None:
                continue
            cur_movie = self._tmdbDetails(cur_result)
            search_results.append(cur_movie)
        if not len(search_results):
            raise TmdbMovieOrPersonNotFound(u'No Movies matching the title (%s)' % org_title)

        # Check if no ui has been requested and therefore just return the raw search results.
        if (self.config['interactive'] == False and self.config['select_first'] == False and self.config['custom_ui'] == None) or not len(search_results):
            return search_results

        # Select the first result (most likely match) or invoke user interaction to select the correct movie
        if self.config['custom_ui'] is not None:
            self.log.debug("Using custom UI %s" % (repr(self.config['custom_ui'])))
            ui = self.config['custom_ui'](config = self.config, log = self.log, searchTerm = org_title)
        else:
            if not self.config['interactive']:
                self.log.debug('Auto-selecting first search result using BaseUI')
                ui = BaseUI(config = self.config, log = self.log, searchTerm = org_title)
            else:
                self.log.debug('Interactivily selecting movie using ConsoleUI')
                ui = ConsoleUI(config = self.config, log = self.log, searchTerm = org_title)
        return ui.selectMovieOrPerson(search_results)
    # end searchTitle()

    def searchTMDB(self, by_id, lang=False):
        """Searches for a film by its TMDB id number.
        Returns a movie data dictionary
        """
        if lang: # Override language
            URL = self.config[u'urls'][u'tmdbid.search'].replace(self.lang_url % self.config['language'], self.lang_url % lang)
        else:
            URL = self.config[u'urls'][u'tmdbid.search']
        id_url = urllib.quote(by_id.encode("utf-8"))
        url = URL % (id_url)
        if self.config['debug_enabled']:        # URL so that raw TMDB XML data can be viewed in a browser
            sys.stderr.write(u'\nDEBUG: XML URL:%s\n\n' % url)

        etree = XmlHandler(url).getEt()

        if etree is None:
            raise TmdbMovieOrPersonNotFound(u'No Movies matching the TMDB number (%s)' % by_id)
        if etree.find(u"movies").find(u"movie"):
            return self.movieDetails(etree.find(u"movies").find(u"movie"))
        else:
            raise TmdbMovieOrPersonNotFound(u'No Movies matching the TMDB number (%s)' % by_id)

    def searchIMDB(self, by_id, lang=False):
        """Searches for a film by its IMDB number.
        Returns a movie data dictionary
        """
        if lang: # Override language
            URL = self.config[u'urls'][u'imdb.search'].replace(self.lang_url % self.config['language'], self.lang_url % lang)
        else:
            URL = self.config[u'urls'][u'imdb.search']
        id_url = urllib.quote(by_id.encode("utf-8"))
        url = URL % (id_url)
        if self.config['debug_enabled']:        # URL so that raw TMDB XML data can be viewed in a browser
            sys.stderr.write(u'\nDEBUG: XML URL:%s\n\n' % url)

        etree = XmlHandler(url).getEt()

        if etree is None:
            raise TmdbMovieOrPersonNotFound(u'No Movies matching the IMDB number (%s)' % by_id)
        if etree.find(u"movies").find(u"movie") == None:
            raise TmdbMovieOrPersonNotFound(u'No Movies matching the IMDB number (%s)' % by_id)

        if self._tmdbDetails(etree.find(u"movies").find(u"movie")).has_key(u'id'):
            return self.searchTMDB(self._tmdbDetails(etree.find(u"movies").find(u"movie"))[u'id'],)
        else:
            raise TmdbMovieOrPersonNotFound(u'No Movies matching the IMDB number (%s)' % by_id)

    def searchHash(self, by_hash, lang=False):
        """Searches for a film by its TMDB id number.
        Returns a movie data dictionary
        """
        if lang: # Override language
            URL = self.config[u'urls'][u'hash.info'].replace(self.lang_url % self.config['language'], self.lang_url % lang)
        else:
            URL = self.config[u'urls'][u'hash.info']
        id_url = urllib.quote(by_hash.encode("utf-8"))
        url = URL % (id_url)
        if self.config['debug_enabled']:        # URL so that raw TMDB XML data can be viewed in a browser
            sys.stderr.write(u'\nDEBUG: XML URL:%s\n\n' % url)

        etree = XmlHandler(url).getEt()

        if etree is None:
            raise TmdbMovieOrPersonNotFound(u'No Movies matching the hash value (%s)' % by_hash)
        if etree.find(u"movies").find(u"movie"):
            return self.movieDetails(etree.find(u"movies").find(u"movie"))
        else:
            raise TmdbMovieOrPersonNotFound(u'No Movies matching the hash value (%s)' % by_hash)


    def searchImage(self, by_id, lang=False, filterout=False):
        """Searches for a film's images URLs by TMDB number.
        Returns a image URL dictionary
        """
        if lang: # Override language
            URL = self.config[u'urls'][u'image.search'].replace(self.lang_url % self.config['language'], self.lang_url % lang)
        else:
            URL = self.config[u'urls'][u'image.search']
        id_url = urllib.quote(by_id.encode("utf-8"))
        url = URL % (id_url)
        if self.config['debug_enabled']:        # URL so that raw TMDB XML data can be viewed in a browser
            sys.stderr.write(u'\nDEBUG: XML URL:%s\n\n' % url)

        etree = XmlHandler(url).getEt()
        if etree is None:
            raise TmdbMovieOrPersonNotFound(u'No Movie matching the TMDB number (%s)' % by_id)
        if not etree.find(u"movies").find(u"movie"):
            raise TmdbMovieOrPersonNotFound(u'No Movie matching the TMDB number (%s)' % by_id)

        cur_poster = {}
        cur_backdrop = {}

        for item in etree.find(u"movies").find(u"movie").getchildren():
            if item.tag.lower() == u"images":
                for image in item.getchildren():
                    if image.tag == u"poster":
                        for poster in image.getchildren():
                            key = poster.get('size')
                            if key in cur_poster.keys():
                                cur_poster[key.strip()].append(poster.get('url').strip())
                            else:
                                cur_poster[key.strip()] = [poster.get('url').strip()]
                    elif image.tag == u"backdrop":
                        for backdrop in image.getchildren():
                            key = backdrop.get('size')
                            if key in cur_backdrop.keys():
                                cur_backdrop[key.strip()].append(backdrop.get('url').strip())
                            else:
                                cur_backdrop[key.strip()] = [backdrop.get('url').strip()]
        images = {}
        if cur_poster.keys():
            for cur_size in [u"original", u"mid", u"cover", u"thumb"]:
                keyvalue = u'poster_%s' % cur_size
                tmp = u''
                if cur_size in cur_poster:
                    for data in cur_poster[cur_size]:
                        tmp+=u'%s,' % data
                if len(tmp):
                    tmp=tmp[:-1]
                    images[keyvalue] = tmp

        if cur_backdrop.keys():
            for cur_size in [u"original", u"mid", u"cover", u"thumb"]:
                keyvalue = u'fanart_%s' % cur_size
                tmp = u''
                if cur_size in cur_backdrop:
                    for data in cur_backdrop[cur_size]:
                        tmp+= u'%s,' % data
                if len(tmp):
                    tmp=tmp[:-1]
                    images[keyvalue] = tmp
        if filterout:
            if images.has_key(filterout):
                return images[filterout]
            else:
                return u''
        else:
            return images
    # end searchImage()

    def searchPeople(self, name, lang=False):
        """Searches for a People by name.
        Returns a list if matching persons and a dictionary of their attributes
        """
        tmp_name = name.strip().replace(u' ',u'+')
        try:
            id_url = urllib.quote(tmp_name.encode("utf-8"))
        except (UnicodeEncodeError, TypeError):
            id_url = urllib.quote(tmp_name)
        if lang: # Override language
            URL = self.config[u'urls'][u'person.search'].replace(self.lang_url % self.config['language'], self.lang_url % lang)
        else:
            URL = self.config[u'urls'][u'person.search']
        url = URL % (id_url)
        if self.config['debug_enabled']:        # URL so that raw TMDB XML data can be viewed in a browser
            sys.stderr.write(u'\nDEBUG: XML URL:%s\n\n' % url)

        etree = XmlHandler(url).getEt()
        if etree is None:
            raise TmdbMovieOrPersonNotFound(u'No People matches found for the name (%s)' % name)
        if not etree.find(u"people").find(u"person"):
            raise TmdbMovieOrPersonNotFound(u'No People matches found for the name (%s)' % name)

        people = []
        for item in etree.find(u"people").getchildren():
            if item.tag == u"person":
                person = {}
                for p in item.getchildren():
                    if p.tag != u'images':
                        person[p.tag] = self.textUtf8(p.text.strip())
                    elif len(p.getchildren()):
                        person[p.tag] = {}
                        for image in p.getchildren():
                            person[p.tag][image.get('size')] = image.get('url').strip()
                people.append(person)

        # Check if no ui has been requested and therefore just return the raw search results.
        if (self.config['interactive'] == False and self.config['select_first'] == False and self.config['custom_ui'] == None) or not len(people):
            return people

        # Select the first result (most likely match) or invoke user interaction to select the correct movie
        if self.config['custom_ui'] is not None:
            self.log.debug("Using custom UI %s" % (repr(self.config['custom_ui'])))
            ui = self.config['custom_ui'](config = self.config, log = self.log, searchTerm = name.strip())
        else:
            if not self.config['interactive']:
                self.log.debug('Auto-selecting first search result using BaseUI')
                ui = BaseUI(config = self.config, log = self.log, searchTerm = name.strip())
            else:
                self.log.debug('Interactivily selecting movie using ConsoleUI')
                ui = ConsoleUI(config = self.config, log = self.log, searchTerm = name.strip())
        return ui.selectMovieOrPerson(people)
    # end searchPeople()


    def personInfo(self, by_id, lang=False):
        """Retrieve a Person's informtaion by their TMDB id.
        Returns dictionary of the persons information attributes
        """
        if lang: # Override language
            URL = self.config[u'urls'][u'person.info'].replace(self.lang_url % self.config['language'], self.lang_url % lang)
        else:
            URL = self.config[u'urls'][u'person.info']
        id_url = urllib.quote(by_id)
        url = URL % (id_url)
        if self.config['debug_enabled']:        # URL so that raw TMDB XML data can be viewed in a browser
            sys.stderr.write(u'\nDEBUG: XML URL:%s\n\n' % url)

        etree = XmlHandler(url).getEt()
        if etree is None:
            raise TmdbMovieOrPersonNotFound(u'No Person match found for the Person ID (%s)' % by_id)
        if not etree.find(u"people").find(u"person"):
            raise TmdbMovieOrPersonNotFound(u'No Person match found for the Person ID (%s)' % by_id)
        person = {}
        elements = ['also_known_as', 'filmography', 'images' ]

        for elem in etree.find(u"people").find(u"person").getchildren():
            if elem.tag in elements:
                if elem.tag == 'also_known_as':
                    alias = []
                    for a in elem.getchildren():
                        if a.text:
                            alias.append(self.textUtf8(a.text.strip()).replace(u'\n',u' '))
                    if alias:
                        person[elem.tag] = alias
                elif elem.tag == 'filmography':
                    movies = []
                    for a in elem.getchildren():
                        details = {}
                        for get in ['url', 'name', 'character', 'job', 'id']:
                            details[get] = a.get(get).strip()
                        if details:
                            movies.append(details)
                    if movies:
                        person[elem.tag] = movies
                elif len(elem.getchildren()):
                    images = {}
                    for image in elem.getchildren():
                        (dirName, fileName) = os.path.split(image.get('url'))
                        (fileBaseName, fileExtension) = os.path.splitext(fileName)
                        if not fileExtension[1:] in self.config[u'image_extentions']:
                            continue
                        if image.get('size') in images.keys():
                            images[image.get('size')]+= u',%s' % image.get('url').strip()
                        else:
                            images[image.get('size')] = u'%s' % image.get('url').strip()
                    if images:
                        person[elem.tag] = images
            else:
                if elem.text:
                    person[elem.tag] = self.textUtf8(elem.text.strip()).replace(u'\n',u' ')
        return person
    # end personInfo()

# end MovieDb class

class Videos(MovieDb):
    """A super class of the MovieDB functionality for the MythTV Netvision plugin functionality.
    This is done to support a common naming framework for all python Netvision plugins no matter their site
    target.
    """
    def __init__(self, apikey, mythtv, interactive, select_first, debug, custom_ui, language, search_all_languages, ):
        """Pass the configuration options
        """
        super(Videos, self).__init__(apikey, mythtv, interactive, select_first, debug, custom_ui, language, search_all_languages, )
    # end __init__()

    error_messages = {'TmdHttpError': u"! Error: A connection error to themoviedb.org was raised (%s)\n", 'TmdXmlError': u"! Error: Invalid XML was received from themoviedb.org (%s)\n", 'TmdBaseError': u"! Error: A user interface error was raised (%s)\n", 'TmdbUiAbort': u"! Error: A user interface input error was raised (%s)\n", }
    key_translation = [{'channel_title': 'channel_title', 'channel_link': 'channel_link', 'channel_description': 'channel_description', 'channel_numresults': 'channel_numresults', 'channel_returned': 'channel_returned', 'channel_startindex': 'channel_startindex'}, {'title': 'item_title', 'item_author': 'item_author', 'releasedate': 'item_pubdate', 'overview': 'item_description', 'url': 'item_link', 'trailer': 'item_url', 'runtime': 'item_duration', 'userrating': 'item_rating', 'width': 'item_width', 'height': 'item_height', 'language': 'item_lang'}]

    def searchForVideos(self, title, pagenumber):
        """Common name for a video search. Used to interface with MythTV plugin NetVision
        """
        def displayMovieData(data):
            '''Parse movie trailer metadata
            return None if no valid data
            return a dictionary of Movie trailer metadata
            '''
            if data == None:
                return None
            if not 'trailer' in data.keys():
                return None
            if data['trailer'] == u'':
                return None

            trailer_data = {}
            for key in self.key_translation[1].keys():
                if key in data.keys():
                    if key == self.thumbnails:
                        thumbnail = data[key].split(u',')
                        trailer_data[self.key_translation[1][key]] = thumbnail[0]
                        continue
                    if key == 'url':    # themoviedb.org always uses Youtube for trailers
                        trailer_data[self.key_translation[1][key]] = data['trailer']
                        continue
                    if key == 'releasedate':
                        c = time.strptime(data[key],"%Y-%m-%d")
                        trailer_data[self.key_translation[1][key]] = time.strftime("%a, %d %b %Y 00:%M:%S GMT",c) # <pubDate>Tue, 14 Jul 2009 17:05:00 GMT</pubDate> <pubdate>Wed, 24 Jun 2009 03:53:00 GMT</pubdate>
                        continue
                    trailer_data[self.key_translation[1][key]] = data[key]
                else:
                    trailer_data[self.key_translation[1][key]] = u''
            trailer_data[self.key_translation[1][u'overview']] = self.overview

            return trailer_data
        # end displayMovieData()

        def movieData(tmdb_id):
            '''Get Movie data by IMDB or TMDB number and return the details
            '''
            try:
                return displayMovieData(self.searchTMDB(tmdb_id))
            except TmdbMovieOrPersonNotFound, msg:
                sys.stderr.write(u"%s\n" % msg)
                return None
            except TmdHttpError, msg:
                sys.stderr.write(self.error_messages['TmdHttpError'] % msg)
                sys.exit(1)
            except TmdXmlError, msg:
                sys.stderr.write(self.error_messages['TmdXmlError'] % msg)
                sys.exit(1)
            except TmdBaseError, msg:
                sys.stderr.write(self.error_messages['TmdBaseError'] % msg)
                sys.exit(1)
            except TmdbUiAbort, msg:
                sys.stderr.write(self.error_messages['TmdbUiAbort'] % msg)
                sys.exit(1)
            except Exception, e:
                sys.stderr.write(u"! Error: Unknown error during a Movie (%s) information lookup\nError(%s)\n" % (tmdb_id, e))
                sys.exit(1)
        # end movieData()

        try:
            data = self.searchTitle(title)
        except TmdbMovieOrPersonNotFound, msg:
            sys.stderr.write(u"%s\n" % msg)
            return []
        except TmdHttpError, msg:
            sys.stderr.write(self.error_messages['TmdHttpError'] % msg)
            sys.exit(1)
        except TmdXmlError, msg:
            sys.stderr.write(self.error_messages['TmdXmlError'] % msg)
            sys.exit(1)
        except TmdBaseError, msg:
            sys.stderr.write(self.error_messages['TmdBaseError'] % msg)
            sys.exit(1)
        except TmdbUiAbort, msg:
            sys.stderr.write(self.error_messages['TmdbUiAbort'] % msg)
            sys.exit(1)
        except Exception, e:
            sys.stderr.write(u"! Error: Unknown error during a Movie Trailer search (%s)\nError(%s)\n" % (title, e))
            sys.exit(1)

        if data == None:
            return None

        # Set the size of the thumbnail graphics that will be returned
        self.thumbnails = 'mid'
        self.key_translation[1][self.thumbnails] = 'item_thumbnail'

        # Channel details and search results
        channel = {'channel_title': u'themoviedb.org', 'channel_link': u'http://themoviedb.org', 'channel_description': u'themoviedb.org is an open “wiki-style” movie database', 'channel_numresults': 0, 'channel_returned': self.page_limit, u'channel_startindex': 0}

        trailers = []
        trailer_total = 0
        starting_index = (int(pagenumber)-1) * self.page_limit
        for match in data:
            if match.has_key('overview'):
                self.overview = match['overview']
            else:
                self.overview = u''
            trailer = movieData(match[u'id'])
            if trailer:
                if starting_index != 0:
                    if not trailer_total > starting_index:
                        continue
                trailers.append(trailer)
                trailer_total+=1
                if self.page_limit == trailer_total:
                    break
        channel['channel_numresults'] = trailer_total
        startindex = trailer_total + starting_index
        if startindex < int(pagenumber) * self.page_limit:
            channel['channel_startindex'] = startindex + 1
        else:
            channel['channel_startindex'] = (int(pagenumber) * self.page_limit) + startindex - 1
        return [[channel, trailers]]
    # end searchForVideos()
# end Videos() class

def main():
    """Simple example of using tmdb_api - it just
    searches for any movies with the word "Avatar" in its tile and returns a list of matches with their summary
    information in a dictionary. And gets movie details using an IMDB# and a TMDB#
    """
    # themoviedb.org api key given by Travis Bell for Mythtv
    api_key = "c27cb71cff5bd76e1a7a009380562c62"
    tmdb = MovieDb(api_key)
    # Output a dictionary of matching movie titles
    print tmdb.searchTitle(u'Avatar')
    print
    # Output a dictionary of matching movie details for IMDB number '0401792'
    print tmdb.searchIMDB(u'0401792')
    # Output a dictionary of matching movie details for TMDB number '19995'
    print tmdb.searchTMDB(u'19995')
# end main()

if __name__ == '__main__':
    main()
