]> git.sesse.net Git - vlc/blob - share/lua/extensions/imdb.lua
7db185903b56ea772666f3ab5fa973f7dcdc6be5
[vlc] / share / lua / extensions / imdb.lua
1 --[[
2  Get information about a movie from IMDb
3
4  Copyright © 2009-2010 VideoLAN and AUTHORS
5
6  Authors:  Jean-Philippe André (jpeg@videolan.org)
7
8  This program is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12
13  This program is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  GNU General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with this program; if not, write to the Free Software
20  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 --]]
22
23 dlg = nil
24 txt = nil
25 function descriptor()
26     return { title = "IMDb - The Internet Movie Database" ;
27              version = "0.1" ;
28              author = "Jean-Philippe André" ;
29              url = 'http://www.imdb.org/';
30              shortdesc = "The Internet Movie Database";
31              description = "<center><b>The Internet Movie Database</b></center><br />"
32                         .. "Get information about movies from the Internet "
33                         .. "Movie Database (IMDb).<br />This Extension will show "
34                         .. "you the cast, a short plot summary and a link to "
35                         .. "the web page on imdb.org." ;
36              capabilities = { "input-listener" } }
37 end
38
39 -- Update title text field. Removes file extensions.
40 function update_title()
41     local item = vlc.input.item()
42     local title = item and item:name()
43     if title ~= nil then
44         title = string.gsub(title, "(.*)(%.%w+)$", "%1")
45     end
46     if title ~= nil then
47         txt:set_text(title)
48     end
49 end
50
51 function input_changed()
52     update_title()
53 end
54
55 function create_dialog()
56     dlg = vlc.dialog("IMDb Search")
57     dlg:add_label("The Internet Movie Database", 1, 1, 4, 1)
58     dlg:add_label("<b>Movie Title</b>", 1, 2, 1, 1)
59     local item = vlc.input.item()
60     txt = dlg:add_text_input(item and item:name() or "", 2, 2, 1, 1)
61     dlg:add_button("Okay", click_okay, 3, 2, 1, 1)
62     dlg:add_button("*", update_title, 4, 2, 1, 1)
63     dlg:show() -- Show, if not already visible
64 end
65
66 function activate()
67     create_dialog()
68 end
69
70 function deactivate()
71 end
72
73 -- Dialog closed
74 function close()
75     -- Deactivate this extension
76     vlc.deactivate()
77 end
78
79 -- Some global variables: widgets
80 list = nil
81 button_open = nil
82 titles = nil
83 html = nil
84
85 function click_okay()
86     vlc.msg.dbg("Searching for " .. txt:get_text() .. " on IMDb")
87
88     if html then
89         dlg:del_widget(html)
90         html = nil
91     end
92
93     if not list then
94         list = dlg:add_list(1, 3, 4, 1)
95         button_open = dlg:add_button("Open", click_open, 1, 4, 4, 1)
96     end
97
98     -- Clear previous results
99     list:clear()
100
101     -- Search IMDb
102     local url = "http://www.imdb.com/find?s=all&q="
103     local title = string.gsub(txt:get_text(), " ", "+")
104     local s, msg = vlc.stream(url .. title)
105     if not s then
106         vlc.msg.warn(msg)
107         return
108     end
109
110     -- Fetch HTML data
111     local data = s:read(65000)
112
113     -- Find titles
114     titles = {}
115     local count = 0
116
117     idxEnd = 1
118     while idxEnd ~= nil do
119         -- Find title types
120         _, idxEnd, titleType = string.find(data, "<b>([^<]*Titles[^<]*)</b>", idxEnd)
121         _, _, nextTitle = string.find(data, "<b>([^<]*Titles[^<]*)</b>", idxEnd)
122         if not titleType then
123             break
124         else
125             -- Find current scope
126             if not nextTitle then
127                 _, _, table = string.find(data, "<table>(.*)</table>", idxEnd)
128             else
129                 nextTitle = string.gsub(nextTitle, "%(", "%%(")
130                 nextTitle = string.gsub(nextTitle, "%)", "%%)")
131                 _, _, table = string.find(data, "<table>(.*)</table>.*"..nextTitle, idxEnd)
132             end
133             -- Find all titles in this scope
134             if not table then break end
135             pos = 0
136             while pos ~= nil do
137                 _, _, link = string.find(table, "<a href=\"([^\"]+title[^\"]+)\"", pos)
138                 if not link then break end -- this would not be normal behavior...
139                 _, pos, title = string.find(table, "<a href=\"" .. link .. "\"[^>]*>([^<]+)</a>", pos)
140                 if not title then break end -- this would not be normal behavior...
141                 _, _, year = string.find(table, "\((%d+)\)", pos)
142                 -- Add this title to the list
143                 count = count + 1
144                 _, _, imdbID = string.find(link, "/([^/]+)/$")
145                 title = replace_html_chars(title)
146                 titles[count] = { id = imdbID ; title = title ; year = year ; link = link }
147             end
148         end
149     end
150
151     for idx, title in ipairs(titles) do
152         list:add_value("[" .. title.id .. "] " .. title.title .. " (" .. title.year .. ")", idx)
153     end
154 end
155
156 function click_open()
157     selection = list:get_selection()
158     if not selection then return 1 end
159     if not html then
160         html = dlg:add_html("Loading IMDb page...", 1, 3, 4, 1)
161         -- userLink = dlg:add_label("", 1, 4, 5, 1)
162     end
163
164     dlg:del_widget(list)
165     dlg:del_widget(button_open)
166     list = nil
167     button_open = nil
168
169     local sel = nil
170     for idx, selectedItem in pairs(selection) do
171         sel = idx
172         break
173     end
174     imdbID = titles[sel].id
175     url = "http://www.imdb.org/title/" .. imdbID .. "/"
176
177     -- userLink:set_text("<a href=\"url\">" .. url .. "</a>")
178
179     local s, msg = vlc.stream(url)
180     if not s then
181         vlc.msg.warn(msg)
182         return
183     end
184
185     data = s:read(65000)
186
187     text = "<h1>" .. titles[sel].title .. " (" .. titles[sel].year .. ")</h1>"
188     text = text .. "<h2>Overview</h2><table>"
189
190     -- Director
191     local director = nil
192     _, nextIdx, _ = string.find(data, "<div id=\"director-info\"", 1, true)
193     if nextIdx then
194         _, _, director = string.find(data, "<a href[^>]+>([%w%s]+)</a>", nextIdx)
195     end
196     if not director then
197         director = "(Unknown)"
198     end
199     text = text .. "<tr><td><b>Director</b></td><td>" .. director .. "</td></tr>"
200
201     -- Main genres
202     local genres = "<tr><td><b>Genres</b></td>"
203     local first = true
204     for genre, _ in string.gmatch(data, "/Sections/Genres/(%w+)/\">") do
205         if first then
206             genres = genres .. "<td>" .. genre .. "</td></tr>"
207         else
208             genres = genres .. "<tr><td /><td>" .. genre .. "</td></tr>"
209         end
210         first = false
211     end
212     text = text .. genres
213
214     -- List main actors
215     local actors = "<tr><td><b>Cast</b></td>"
216     first = true
217     for nm, char in string.gmatch(data, "<td class=\"nm\"><a[^>]+>([%w%s]+)</a></td><td class=\"ddd\"> ... </td><td class=\"char\"><a[^>]+>([%w%s]+)</a>") do
218         if not first then
219             actors = actors .. "<tr><td />"
220         end
221         actors = actors .. "<td>" .. nm .. "</td><td><i>" .. char .. "</i></td></tr>"
222         first = false
223     end
224     text = text .. actors .. "</table>"
225
226     text = text .. "<h2>Plot Summary</h2>"
227     s, msg = vlc.stream(url .. "plotsummary")
228     if not s then
229         vlc.msg.warn(msg)
230         return
231     end
232     data = s:read(65000)
233
234     -- We read only the first summary
235     _, _, summary = string.find(data, "<p class=\"plotpar\">([^<]+)")
236     if not summary then
237         summary = "(Unknown)"
238     end
239     text = text .. "<p>" .. summary .. "</p>"
240     text = text .. "<p><h2>Source IMDb</h2><a href=\"" .. url .. "\">" .. url .. "</a></p>"
241
242     html:set_text(text)
243 end
244
245 -- Convert some HTML characters into UTF8
246 function replace_html_chars(txt)
247     if not txt then return nil end
248     -- return vlc.strings.resolve_xml_special_chars(txt)
249     for num in string.gmatch(txt, "&#x(%x+);") do
250         -- Convert to decimal (any better way?)
251         dec = 0
252         for c in string.gmatch(num, "%x") do
253             cc = string.byte(c) - string.byte("0")
254             if (cc >= 10 or cc < 0) then
255                 cc = string.byte(string.lower(c)) - string.byte("a") + 10
256             end
257             dec = dec * 16 + cc
258         end
259         txt = string.gsub(txt, "&#x" .. num .. ";", string.char(dec))
260     end
261     return txt
262 end
263