~ubuntu-branches/ubuntu/quantal/griffith/quantal

« back to all changes in this revision

Viewing changes to lib/plugins/movie/PluginMovieAniDB.py

  • Committer: Package Import Robot
  • Author(s): Piotr Ożarowski
  • Date: 2011-12-18 21:15:25 UTC
  • mfrom: (1.1.17) (3.1.11 sid)
  • Revision ID: package-import@ubuntu.com-20111218211525-endbgt82m3jh9gde
Tags: 0.13-1
* New upstream release
* VCS-* fields removed (berlios will be shutdown)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# -*- coding: utf-8 -*-
2
2
 
3
 
__revision__ = '$Id: PluginMovieAniDB.py 1445 2010-09-15 18:05:35Z mikej06 $'
 
3
__revision__ = '$Id: PluginMovieAniDB.py 1594 2011-09-22 21:00:44Z mikej06 $'
4
4
 
5
 
# Copyright (c) 2005-2009 Piotr Ożarowski
 
5
# Copyright © 2005-2011 Piotr Ożarowski
6
6
#
7
7
# This program is free software; you can redistribute it and/or modify
8
8
# it under the terms of the GNU General Public License as published by
21
21
# You may use and distribute this software under the terms of the
22
22
# GNU General Public License, version 2 or later
23
23
 
24
 
import gutils, movie
25
 
import string, re
 
24
import urllib2
 
25
import logging
 
26
from datetime import datetime, timedelta
 
27
from locale import getdefaultlocale
 
28
from os.path import getmtime, isfile, join
 
29
 
 
30
from lxml import etree
 
31
 
26
32
from gutils import decompress
 
33
from movie import Movie, SearchMovie
 
34
 
 
35
log = logging.getLogger("Griffith")
27
36
 
28
37
plugin_name         = 'AnimeDB'
29
38
plugin_description  = 'Anime DataBase'
31
40
plugin_language     = _('English')
32
41
plugin_author       = 'Piotr Ożarowski'
33
42
plugin_author_email = 'piotr@griffith.cc'
34
 
plugin_version      = '2.8'
35
 
 
36
 
aid_pattern = re.compile('[?&;]aid=(\d+)')
37
 
 
38
 
class Plugin(movie.Movie):
39
 
    def __init__(self, id):
40
 
        self.encode = 'utf-8'
41
 
        if string.find(id, 'http://') != -1:
42
 
            self.url = str(id)
43
 
            self.movie_id = 'anidb'
44
 
        else:
45
 
            self.movie_id = str(id)
46
 
            self.url = "http://anidb.net/perl-bin/animedb.pl?show=anime&aid=%s" % self.movie_id
 
43
plugin_version      = '3.0'
 
44
 
 
45
ANIME_TITLES_URL = 'http://anidb.net/api/animetitles.xml.gz'
 
46
ANIME_IMG_URL = 'http://img7.anidb.net/pics/anime/'
 
47
#ANIME_WEB_URL = 'http://anidb.net/perl-bin/animedb.pl?show=anime&aid='
 
48
ANIME_WEB_URL = 'http://anidb.net/a'
 
49
REQUEST = "http://api.anidb.net:9001/httpapi?request=anime&client=%(client)s&clientver=%(version)s&protover=%(protocol)s&aid="
 
50
REQUEST %= dict(client='griffith', version=1, protocol=1)
 
51
 
 
52
lang = getdefaultlocale()[0][:2]  # TODO: get it from settings
 
53
 
 
54
 
 
55
class Plugin(Movie):
 
56
    def __init__(self, aid):
 
57
        self.encode = None
 
58
        self.useurllib2 = True
 
59
        self._aid = aid
 
60
        self.url = REQUEST + aid
47
61
 
48
62
    def initialize(self):
49
 
        self.page = decompress(self.page)
50
 
        if self.movie_id == 'anidb':
51
 
            aid =  aid_pattern.search(self.page)
52
 
            if aid:
53
 
                self.movie_id = aid.groups()[0]
54
 
                self.url = "http://anidb.net/perl-bin/animedb.pl?show=anime&aid=%s" % self.movie_id
55
 
            else:
56
 
                return False
57
 
        self.page = gutils.after(self.page, 'id="layout-content"')
58
 
        pos = string.find(self.page, 'class="g_section anime_episodes">')
59
 
        if pos >0:
60
 
            self.page = self.page[:pos]
 
63
        if not self.page.startswith('<?xml'):
 
64
            raise Exception('page not in XML format')
 
65
        self._xml = etree.fromstring(self.page)
61
66
 
62
67
    def get_image(self):
63
 
        match = re.search('img\d*.anidb.net/pics/anime/\d*.jpg', self.page)
64
 
        if match is not None:
65
 
            self.image_url = 'http://' + match.group()
66
 
        else:
67
 
            self.image_url = ''
 
68
        self.image_url = ''
 
69
        node = self._xml.find('picture')
 
70
        if node is not None:
 
71
            self.image_url = ANIME_IMG_URL + node.text
68
72
 
69
73
    def get_o_title(self):
70
 
        self.o_title = gutils.trim(self.page, '<span class="i_icon i_audio_ja" title=" language: japanese"><span>ja</span></span>', '</td>')
71
 
        self.o_title = gutils.trim(self.o_title, '<label>', '</label>')
 
74
        self.o_title = ''
 
75
        node = self._xml.find('titles/title[@type="main"]')
 
76
        if node is not None:
 
77
            self.o_title = node.text
72
78
 
73
79
    def get_title(self):
74
 
        self.title = gutils.trim(self.page, '<h1 class="anime">Anime: ', '</h1>')
 
80
        node = self._xml.xpath("titles/title[@xml:lang='%s' and @type='official']" % lang)
 
81
        if node:
 
82
            self.title = node[0].text
 
83
        else:
 
84
            node = self._xml.xpath("titles/title[@type='official']")
 
85
            if node:
 
86
                self.title = node[0].text
75
87
 
76
88
    def get_director(self):
77
 
        self.director = gutils.trim(self.page, '>Direction (&#x76E3;&#x7763;', '</tr>')
78
 
        self.director = gutils.after(gutils.trim(self.director, '<a ', '</a>'), '>')
 
89
        self.director = ', '.join(n.text for n in self._xml.xpath('creators/name[@type="Direction"]'))
79
90
 
80
91
    def get_plot(self):
81
 
        self.plot = gutils.regextrim(self.page, 'class="(g_bubble )*desc">', '</div>')
82
 
        self.plot = self.plot.replace('<br/>', '\n')
 
92
        self.plot = ''
 
93
        node = self._xml.find('description')
 
94
        if node is not None:
 
95
            self.plot = node.text
83
96
 
84
97
    def get_year(self):
85
 
        self.year = gutils.trim(self.page, '"field">Year', '</td>')
86
 
        self.year = gutils.after(self.year, '"value">')[-4:]
 
98
        self.year = 0
 
99
        node = self._xml.xpath('episodes/episode[title="Complete Movie"]')
 
100
        if node:
 
101
            node = node[0].find('airdate')
 
102
            if node is not None:
 
103
                self.year = node.text[:4]
 
104
        else:
 
105
            node = self._xml.find('startdate')
 
106
            if node is not None:
 
107
                self.year = node.text[:4]
 
108
        # XXX: should we take the first child if "Complete Movie" is missing?
87
109
 
88
110
    def get_runtime(self):
89
 
        self.runtime = gutils.trim(self.page, '<label>Complete Movie</label>', '</tr>')
90
 
        self.runtime = gutils.trim(self.runtime, '<td class="duration">', 'm')
 
111
        self.runtime = 0
 
112
        node = self._xml.xpath('episodes/episode[title="Complete Movie"]')
 
113
        if node:
 
114
            self.runtime = node[0].find('length').text
91
115
 
92
116
    def get_genre(self):
93
 
        self.genre = gutils.trim(self.page, '>Categories<', '</td>')
94
 
        self.genre = gutils.after(self.genre, 'value">')
95
 
        self.genre = gutils.strip_tags(self.genre)
96
 
        if len(self.genre) and self.genre.endswith('- similar'):
97
 
            self.genre =  self.genre[:-9]
98
 
        elif self.genre == '-':
99
 
            self.genre = ''
100
 
        self.genre = string.replace(self.genre, '\n', '')
 
117
        nodes = self._xml.xpath('categories/category/name')
 
118
        self.genre = ', '.join(n.text for n in nodes)
101
119
 
102
120
    def get_cast(self):
103
 
        self.cast = 'Characters:\n---------------'
104
 
        castv = gutils.trim(self.page, '<table id="characterlist" class="characterlist">', '</table>')
105
 
        if castv != '':
106
 
            castparts = string.split(castv, '<tr ')
107
 
            for index in range(2, len(castparts), 1):
108
 
                castpart = castparts[index]
109
 
                castcharacter = gutils.clean(gutils.trim(castpart, '<td rowspan="1" class="name">', '</td>'))
110
 
                castentity = gutils.clean(gutils.trim(castpart, '<td rowspan="1" class="entity">', '</td>'))
111
 
                castactor = gutils.clean(gutils.trim(castpart, '<td class="name"><a href="animedb.pl?show=creator&amp;creatorid=', 'd>'))
112
 
                castactor = gutils.clean(gutils.trim(castactor, '">', '</t'))
113
 
                if castv == ' ':
114
 
                    castactor = 'unknown'
115
 
                castrelation = gutils.clean(gutils.trim(castpart, '<td rowspan="1" class="relation">', '</td>'))
116
 
                castappearance = gutils.clean(gutils.trim(castpart, '<td rowspan="1" class="eprange">', '</td>'))
117
 
                self.cast += '\n\n' + '[' + castcharacter + '] voiced by ' + castactor + '\n' + castentity + '; ' + castrelation + '; appears in episodes: ' + castappearance
118
 
 
119
 
    def get_classification(self):
120
 
        self.classification = ''
 
121
        nodes = self._xml.xpath('characters/character[@type="main character in"]')
 
122
        self.cast = ''
 
123
        for node in nodes:
 
124
            name = node.find('name').text
 
125
            actor = node.find('seiyuu').text
 
126
            self.cast += "[%s] voiced by %s\n" % (name, actor)
121
127
 
122
128
    def get_studio(self):
123
 
        self.studio = gutils.trim(self.page, '<tr class="producers">', '</tr>')
124
 
        if self.studio == '':
125
 
            self.studio = gutils.trim(self.page, '<tr class="g_odd producers">', '</tr>')
126
 
        self.studio = gutils.trim(self.studio, '<td class="value">', '</td>')
127
 
        self.studio = gutils.strip_tags(self.studio)
128
 
        if len(self.studio) and self.studio[:2] == " (":
129
 
            self.studio = self.studio[2:]
130
 
            if self.studio[len(self.studio)-1:] == ')':
131
 
                self.studio = self.studio[:len(self.studio)-1]
132
 
        self.studio = string.replace(self.studio, '\n', '')
 
129
        self.studio = ', '.join(n.text for n in self._xml.xpath('creators/name[@type="Animation Production"]'))
133
130
 
134
131
    def get_o_site(self):
135
 
        self.o_site = gutils.trim(self.page, '<th class="field">Resources</th>', '</tr>') #class varies, tag used
136
 
        self.o_site = gutils.regextrim(self.o_site, '<a class="official[^"]*" href="', '" rel="anidb::extern">Official page')
 
132
        self.o_site = ''
 
133
        node = self._xml.find('url')
 
134
        if node is not None:
 
135
            self.o_site = node.text
137
136
 
138
137
    def get_site(self):
139
 
        self.site = self.url
140
 
 
141
 
    def get_trailer(self):
142
 
        self.trailer = ''
143
 
 
144
 
    def get_country(self):
145
 
        self.country = ''
 
138
        self.site = ANIME_WEB_URL + self._aid
146
139
 
147
140
    def get_rating(self):
148
 
        self.rating = gutils.clean(gutils.after(gutils.trim(self.page, '<span class="rating', '</a>'), '>'))
149
 
        if self.rating:
150
 
            try:
151
 
                self.rating = str(round(float(self.rating)))
152
 
            except:
153
 
                self.rating = ''
 
141
        rating = self._xml.find('ratings/permanent')
 
142
        if rating is not None:
 
143
            self.rating = str(round(float(rating.text)))
154
144
 
155
145
    def get_notes(self):
156
146
        self.notes = ''
157
147
        # ...type and episodes
158
 
        atype = gutils.trim(self.page, '"field">Type', '</td>')
159
 
        atype = gutils.clean(atype)
160
 
        if atype != '':
161
 
            self.notes += "Type: %s\n" % atype
162
 
        episodes = gutils.trim(self.page, '>Episode list<', '</table>')
163
 
        if episodes != '':
164
 
            parts = string.split(episodes, '<tr ')
165
 
            for index in range(2, len(parts), 1):
166
 
                part = parts[index]
167
 
                nr = gutils.clean(gutils.trim(part, 'class="id eid">', '</td>'))
168
 
                title = gutils.clean(gutils.after(gutils.trim(part, '<label', '</td>'), '>'))
169
 
                duration = gutils.clean(gutils.trim(part, 'class="duration">', '</td>'))
170
 
                airdate = gutils.clean(gutils.trim(part, 'class="date airdate">', '</td>'))
171
 
                self.notes += '\n' + nr + ': ' + title + ' (' + duration + ', ' + airdate + ')'
172
 
 
173
 
    def get_screenplay(self):
174
 
        self.screenplay = gutils.trim(self.page, 'Script/Screenplay (&#x811A;&#x672C;', '</tr>')
175
 
        self.screenplay = gutils.after(gutils.trim(self.screenplay, '<a ', '</a>'), '>')
176
 
 
177
 
class SearchPlugin(movie.SearchMovie):
178
 
    def __init__(self):
179
 
        self.encode = 'utf-8'
180
 
        self.original_url_search = 'http://anidb.net/perl-bin/animedb.pl?show=animelist&do.search=search&adb.search='
181
 
        self.translated_url_search = 'http://anidb.net/perl-bin/animedb.pl?show=animelist&do.search=search&adb.search='
182
 
 
183
 
    def search(self,parent_window):
184
 
        self.open_search(parent_window)
185
 
        self.page = decompress(self.page)
186
 
 
187
 
        tmp = string.find(self.page, '>Anime List - Search for: ')
188
 
        if tmp == -1:        # already a movie page
189
 
            self.page = 'movie'
190
 
        else:            # multiple matches
191
 
            self.page = gutils.trim(self.page, 'class="animelist"', '</table>');
192
 
            self.page = gutils.after(self.page, '</tr>');
193
 
 
194
 
        return self.page
 
148
        type_ = self._xml.find('type')
 
149
        if type_ is not None:
 
150
            self.notes += "Type: %s\n" % type_.text
 
151
        episodes = {}
 
152
        for node in self._xml.xpath('episodes/episode'):
 
153
            try:
 
154
                key = int(node.find('epno').text)
 
155
            except:
 
156
                key = node.find('epno').text
 
157
            titles = {}
 
158
            for tnode in node.xpath('title'):
 
159
                titles[tnode.attrib['{http://www.w3.org/XML/1998/namespace}lang']] = tnode.text
 
160
            duration = node.find('length').text
 
161
            airdate = node.find('airdate')
 
162
            airdate = airdate.text if airdate is not None else None
 
163
            episodes[key] = dict(titles=titles, duration=duration, airdate=airdate)
 
164
        for key, details in sorted(episodes.iteritems()):
 
165
            self.notes += "\n%s: " % key
 
166
            self.notes += details['titles'].get(lang, details['titles']['en'])
 
167
            self.notes += " (%s" % details['duration']
 
168
            self.notes += _(' min')
 
169
            if details['airdate']:
 
170
                self.notes += ", %s)" % details['airdate']
 
171
            else:
 
172
                self.notes += ')'
 
173
 
 
174
 
 
175
class SearchPlugin(SearchMovie):
 
176
    original_url_search = 'http://anidb.net/'
 
177
    translated_url_search = 'http://anidb.net/'
 
178
 
 
179
    def load_titles(self, fpath, parent_window):
 
180
        # animetitles.xml.gz is updated once a day
 
181
        remote = None
 
182
        download = True
 
183
        if isfile(fpath):
 
184
            cache_last_modified = datetime.fromtimestamp(getmtime(fpath))
 
185
            if cache_last_modified > datetime.now() - timedelta(days=1):
 
186
                download = False
 
187
            else:
 
188
                remote = urllib2.urlopen(ANIME_TITLES_URL)
 
189
                last_modified = datetime(*remote.info().getdate('Last-Modified')[:7])
 
190
                if cache_last_modified >= last_modified:
 
191
                    download = False
 
192
                remote.close()
 
193
 
 
194
        if download:
 
195
            log.info('downloading title list from %s' % ANIME_TITLES_URL)
 
196
            self.url = ''
 
197
            self.title = ANIME_TITLES_URL
 
198
            self.open_search(parent_window, fpath)
 
199
 
 
200
        return etree.fromstring(decompress(open(fpath, 'rb').read()))
 
201
 
 
202
    def search(self, parent_window):
 
203
        self._search_results = []
 
204
        exml = self.load_titles(join(self.locations['home'], 'animetitles.xml.gz'), parent_window)
 
205
        for node in exml.xpath("anime[contains(., '%s')]" % self.title.replace("'", r"\'")):
 
206
            aid = node.attrib['aid']
 
207
            title = node.xpath("title[@xml:lang='%s']" % lang)
 
208
            # XXX: how about xpath with both cases and sorting results later?
 
209
            if not title:
 
210
                title = node.xpath('title[@type="main"]')[0].text
 
211
            else:
 
212
                title = title[0].text
 
213
            self._search_results.append((aid, title))
 
214
 
 
215
        return self._search_results
195
216
 
196
217
    def get_searches(self):
197
 
        if self.page == 'movie':    # already a movie page
198
 
            self.number_results = 1
199
 
            self.ids.append(self.url)
200
 
            self.titles.append(self.title)
201
 
        else:            # multiple matches
202
 
            elements = string.split(self.page,"</tr>")
203
 
            self.number_results = elements[-1]
204
 
 
205
 
            if len(elements[0]):
206
 
                for element in elements:
207
 
                    aid = aid_pattern.search(element)
208
 
                    if not aid:
209
 
                        continue
210
 
                    title = gutils.clean(gutils.trim(element, '<td class="name">', '</a>'))
211
 
                    type = gutils.clean(gutils.after(gutils.trim(element, '<td class="type', '</td>'), '>'))
212
 
                    self.ids.append(aid.groups()[0])
213
 
                    if type:
214
 
                        self.titles.append(title + ' (' + type + ')')
215
 
                    else:
216
 
                        self.titles.append(title)
217
 
            else:
218
 
                self.number_results = 0
 
218
        del self.ids[:]
 
219
        del self.titles[:]
 
220
        self.number_results = len(self._search_results)
 
221
        for aid, title in self._search_results:
 
222
            self.ids.append(aid)
 
223
            self.titles.append(title)
219
224
 
220
225
#
221
226
# Plugin Test
228
233
    # dict { movie_id -> [ expected result count for original url, expected result count for translated url ] }
229
234
    #
230
235
    test_configuration = {
231
 
        'Hellsing' : [ 9, 9 ]
 
236
        'Hellsing': [ 1, 1 ]
232
237
    }
233
238
 
234
239
 
241
246
    #        * or the expected value
242
247
    #
243
248
    test_configuration = {
244
 
        '32' : {
 
249
        '32': {
245
250
            'title'               : 'Hellsing',
246
 
            'o_title'             : u'ヘルシング',
 
251
            'o_title'             : 'Hellsing',
247
252
            'director'            : 'Urata Yasunori',
248
253
            'plot'                : True,
249
 
            'cast'                : u'Characters:\n\
250
 
---------------\n\
251
 
\n\
252
 
[Alucard] voiced by Nakata Jouji\n\
253
 
male; main character in; appears in episodes: -\n\
254
 
\n\
255
 
[Incognito] voiced by Yamazaki Takumi\n\
256
 
-; main character in; appears in episodes: 8-13\n\
257
 
\n\
 
254
            'cast'                : u'[Alucard] voiced by Nakata Jouji\n\
 
255
[Sir Integral Fairbrook Wingates Hellsing] voiced by Sakakibara Yoshiko\n\
258
256
[Seras Victoria] voiced by Orikasa Fumiko\n\
259
 
female; main character in; appears in episodes: -\n\
260
 
\n\
261
 
[Sir Integral Fairbrook Wingates Hellsing] voiced by Sakakibara Yoshiko\n\
262
 
22, female; main character in; appears in episodes: -\n\
263
 
\n\
264
 
[Alexander Anderson] voiced by Nozawa Nachi\n\
265
 
male; secondary cast in; appears in episodes: -\n\
266
 
\n\
267
 
[Enrico Maxwell] voiced by Tanaka Hideyuki\n\
268
 
male; secondary cast in; appears in episodes: -\n\
269
 
\n\
270
 
[Helena] voiced by Hiramatsu Akiko\n\
271
 
female; secondary cast in; appears in episodes: 8, 11\n\
272
 
\n\
273
 
[Walter C. Dornez] voiced by Kiyokawa Motomu\n\
274
 
male; secondary cast in; appears in episodes: -\n\
275
 
\n\
276
 
[Hellsing Organization] voiced by \n\
277
 
Organisation; appears in; appears in episodes: -\n\
278
 
\n\
279
 
[Iscariot Organization] voiced by \n\
280
 
Organisation; appears in; appears in episodes: -\n\
281
 
\n\
282
 
[Police Officer inside Heli (ヘリ機内警察官)] voiced by Andy Holyfield\n\
283
 
-; appears in; appears in episodes: 8',
 
257
[Incognito] voiced by Yamazaki Takumi',
284
258
            'country'             : False,
285
 
            'genre'               : 'Action, Contemporary Fantasy, Cops, Fantasy, Gunfights, Horror, Law and Order, Manga, Seinen, Special Squads, Vampires, Violence',
 
259
            'genre'               : 'Law and Order, Alternative Present, Horror, Vampires, Gunfights, Action, Contemporary Fantasy, United Kingdom, Earth, Europe, Special Squads, Cops, Manga, Seinen, Violence, Fantasy, Present',
286
260
            'classification'      : False,
287
261
            'studio'              : False,
288
262
            'o_site'              : 'http://www.gonzo.co.jp/works/0102.html',
289
 
            'site'                : 'http://anidb.net/perl-bin/animedb.pl?show=anime&aid=32',
 
263
            'site'                : 'http://anidb.net/a32',
290
264
            'trailer'             : False,
291
 
            'year'                : 2002,
 
265
            'year'                : 2001,
292
266
            'notes'               : True,
293
267
            'runtime'             : 0,
294
268
            'image'               : True,
295
269
            'rating'              : 8,
296
270
            'cameraman'           : False,
297
 
            'screenplay'          : 'Konaka Chiaki'
 
271
            'screenplay'          : False
298
272
        },
299
273
    }