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'
36
aid_pattern = re.compile('[?&;]aid=(\d+)')
38
class Plugin(movie.Movie):
39
def __init__(self, id):
41
if string.find(id, 'http://') != -1:
43
self.movie_id = 'anidb'
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'
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)
52
lang = getdefaultlocale()[0][:2] # TODO: get it from settings
56
def __init__(self, aid):
58
self.useurllib2 = True
60
self.url = REQUEST + aid
48
62
def initialize(self):
49
self.page = decompress(self.page)
50
if self.movie_id == 'anidb':
51
aid = aid_pattern.search(self.page)
53
self.movie_id = aid.groups()[0]
54
self.url = "http://anidb.net/perl-bin/animedb.pl?show=anime&aid=%s" % self.movie_id
57
self.page = gutils.after(self.page, 'id="layout-content"')
58
pos = string.find(self.page, 'class="g_section anime_episodes">')
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)
62
67
def get_image(self):
63
match = re.search('img\d*.anidb.net/pics/anime/\d*.jpg', self.page)
65
self.image_url = 'http://' + match.group()
69
node = self._xml.find('picture')
71
self.image_url = ANIME_IMG_URL + node.text
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>')
75
node = self._xml.find('titles/title[@type="main"]')
77
self.o_title = node.text
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)
82
self.title = node[0].text
84
node = self._xml.xpath("titles/title[@type='official']")
86
self.title = node[0].text
76
88
def get_director(self):
77
self.director = gutils.trim(self.page, '>Direction (監督', '</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"]'))
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')
93
node = self._xml.find('description')
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:]
99
node = self._xml.xpath('episodes/episode[title="Complete Movie"]')
101
node = node[0].find('airdate')
103
self.year = node.text[:4]
105
node = self._xml.find('startdate')
107
self.year = node.text[:4]
108
# XXX: should we take the first child if "Complete Movie" is missing?
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')
112
node = self._xml.xpath('episodes/episode[title="Complete Movie"]')
114
self.runtime = node[0].find('length').text
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 == '-':
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)
102
120
def get_cast(self):
103
self.cast = 'Characters:\n---------------'
104
castv = gutils.trim(self.page, '<table id="characterlist" class="characterlist">', '</table>')
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&creatorid=', 'd>'))
112
castactor = gutils.clean(gutils.trim(castactor, '">', '</t'))
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
119
def get_classification(self):
120
self.classification = ''
121
nodes = self._xml.xpath('characters/character[@type="main character in"]')
124
name = node.find('name').text
125
actor = node.find('seiyuu').text
126
self.cast += "[%s] voiced by %s\n" % (name, actor)
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"]'))
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')
133
node = self._xml.find('url')
135
self.o_site = node.text
138
137
def get_site(self):
141
def get_trailer(self):
144
def get_country(self):
138
self.site = ANIME_WEB_URL + self._aid
147
140
def get_rating(self):
148
self.rating = gutils.clean(gutils.after(gutils.trim(self.page, '<span class="rating', '</a>'), '>'))
151
self.rating = str(round(float(self.rating)))
141
rating = self._xml.find('ratings/permanent')
142
if rating is not None:
143
self.rating = str(round(float(rating.text)))
155
145
def get_notes(self):
157
147
# ...type and episodes
158
atype = gutils.trim(self.page, '"field">Type', '</td>')
159
atype = gutils.clean(atype)
161
self.notes += "Type: %s\n" % atype
162
episodes = gutils.trim(self.page, '>Episode list<', '</table>')
164
parts = string.split(episodes, '<tr ')
165
for index in range(2, len(parts), 1):
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 + ')'
173
def get_screenplay(self):
174
self.screenplay = gutils.trim(self.page, 'Script/Screenplay (脚本', '</tr>')
175
self.screenplay = gutils.after(gutils.trim(self.screenplay, '<a ', '</a>'), '>')
177
class SearchPlugin(movie.SearchMovie):
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='
183
def search(self,parent_window):
184
self.open_search(parent_window)
185
self.page = decompress(self.page)
187
tmp = string.find(self.page, '>Anime List - Search for: ')
188
if tmp == -1: # already a movie page
190
else: # multiple matches
191
self.page = gutils.trim(self.page, 'class="animelist"', '</table>');
192
self.page = gutils.after(self.page, '</tr>');
148
type_ = self._xml.find('type')
149
if type_ is not None:
150
self.notes += "Type: %s\n" % type_.text
152
for node in self._xml.xpath('episodes/episode'):
154
key = int(node.find('epno').text)
156
key = node.find('epno').text
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']
175
class SearchPlugin(SearchMovie):
176
original_url_search = 'http://anidb.net/'
177
translated_url_search = 'http://anidb.net/'
179
def load_titles(self, fpath, parent_window):
180
# animetitles.xml.gz is updated once a day
184
cache_last_modified = datetime.fromtimestamp(getmtime(fpath))
185
if cache_last_modified > datetime.now() - timedelta(days=1):
188
remote = urllib2.urlopen(ANIME_TITLES_URL)
189
last_modified = datetime(*remote.info().getdate('Last-Modified')[:7])
190
if cache_last_modified >= last_modified:
195
log.info('downloading title list from %s' % ANIME_TITLES_URL)
197
self.title = ANIME_TITLES_URL
198
self.open_search(parent_window, fpath)
200
return etree.fromstring(decompress(open(fpath, 'rb').read()))
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?
210
title = node.xpath('title[@type="main"]')[0].text
212
title = title[0].text
213
self._search_results.append((aid, title))
215
return self._search_results
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]
206
for element in elements:
207
aid = aid_pattern.search(element)
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])
214
self.titles.append(title + ' (' + type + ')')
216
self.titles.append(title)
218
self.number_results = 0
220
self.number_results = len(self._search_results)
221
for aid, title in self._search_results:
223
self.titles.append(title)
241
246
# * or the expected value
243
248
test_configuration = {
245
250
'title' : 'Hellsing',
246
'o_title' : u'ヘルシング',
251
'o_title' : 'Hellsing',
247
252
'director' : 'Urata Yasunori',
249
'cast' : u'Characters:\n\
252
[Alucard] voiced by Nakata Jouji\n\
253
male; main character in; appears in episodes: -\n\
255
[Incognito] voiced by Yamazaki Takumi\n\
256
-; main character in; appears in episodes: 8-13\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\
261
[Sir Integral Fairbrook Wingates Hellsing] voiced by Sakakibara Yoshiko\n\
262
22, female; main character in; appears in episodes: -\n\
264
[Alexander Anderson] voiced by Nozawa Nachi\n\
265
male; secondary cast in; appears in episodes: -\n\
267
[Enrico Maxwell] voiced by Tanaka Hideyuki\n\
268
male; secondary cast in; appears in episodes: -\n\
270
[Helena] voiced by Hiramatsu Akiko\n\
271
female; secondary cast in; appears in episodes: 8, 11\n\
273
[Walter C. Dornez] voiced by Kiyokawa Motomu\n\
274
male; secondary cast in; appears in episodes: -\n\
276
[Hellsing Organization] voiced by \n\
277
Organisation; appears in; appears in episodes: -\n\
279
[Iscariot Organization] voiced by \n\
280
Organisation; appears in; appears in episodes: -\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,
296
270
'cameraman' : False,
297
'screenplay' : 'Konaka Chiaki'