]> git.sesse.net Git - vlc/blob - share/lua/extensions/imdb.lua
Prefer use of function references for buttons
[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              description = "<center><b>The Internet Movie Database</b></center>\n"
31                         .. "Get information about movies from the Internet "
32                         .. "Movie Database (IMDb).\nThis Extension will show "
33                         .. "you the cast, a short plot summary and a link to "
34                         .. "the web page on imdb.org." ;
35              capabilities = { "input-listener" } }
36 end
37
38 -- Update title text field. Removes file extensions.
39 function update_title()
40     local item = vlc.input.item()
41     local title = item and item:name()
42     if title ~= nil then
43         title = string.gsub(title, "(.*)(%.%w+)$", "%1")
44     end
45     if title ~= nil then
46         txt:set_text(title)
47     end
48 end
49
50 function input_changed()
51     update_title()
52 end
53
54 function create_dialog()
55     dlg = vlc.dialog("IMDb Search")
56     dlg:add_label("The Internet Movie Database", 1, 1, 4, 1)
57     dlg:add_label("<b>Movie Title</b>", 1, 2, 1, 1)
58     local item = vlc.input.item()
59     txt = dlg:add_text_input(item and item:name() or "", 2, 2, 1, 1)
60     dlg:add_button("Okay", click_okay, 3, 2, 1, 1)
61     dlg:add_button("*", update_title, 4, 2, 1, 1)
62     dlg:show() -- Show, if not already visible
63 end
64
65 function activate()
66     create_dialog()
67 end
68
69 function deactivate()
70 end
71
72 -- Dialog closed
73 function close()
74     -- Deactivate this extension
75     vlc.deactivate()
76 end
77
78 -- Some global variables: widgets
79 list = nil
80 button_open = nil
81 titles = nil
82 html = nil
83
84 function click_okay()
85     vlc.msg.dbg("Searching for " .. txt:get_text() .. " on IMDb")
86
87     if html then
88         dlg:del_widget(html)
89         html = nil
90     end
91
92     if not list then
93         list = dlg:add_list(1, 3, 4, 1)
94         button_open = dlg:add_button("Open", click_open, 1, 4, 4, 1)
95     end
96
97     -- Clear previous results
98     list:clear()
99
100     -- Search IMDb
101     local url = "http://www.imdb.com/find?s=all&q="
102     local title = string.gsub(txt:get_text(), " ", "+")
103     local s, msg = vlc.stream(url .. title)
104     if not s then
105         vlc.msg.warn(msg)
106         return
107     end
108
109     -- Fetch HTML data
110     local data = s:read(65000)
111
112     -- Find titles
113     titles = {}
114     local count = 0
115
116     idxEnd = 1
117     while idxEnd ~= nil do
118         -- Find title types
119         _, idxEnd, titleType = string.find(data, "<b>([^<]*Titles[^<]*)</b>", idxEnd)
120         _, _, nextTitle = string.find(data, "<b>([^<]*Titles[^<]*)</b>", idxEnd)
121         if not titleType then
122             break
123         else
124             -- Find current scope
125             if not nextTitle then
126                 _, _, table = string.find(data, "<table>(.*)</table>", idxEnd)
127             else
128                 nextTitle = string.gsub(nextTitle, "%(", "%%(")
129                 nextTitle = string.gsub(nextTitle, "%)", "%%)")
130                 _, _, table = string.find(data, "<table>(.*)</table>.*"..nextTitle, idxEnd)
131             end
132             -- Find all titles in this scope
133             if not table then break end
134             pos = 0
135             while pos ~= nil do
136                 _, _, link = string.find(table, "<a href=\"([^\"]+title[^\"]+)\"", pos)
137                 if not link then break end -- this would not be normal behavior...
138                 _, pos, title = string.find(table, "<a href=\"" .. link .. "\"[^>]*>([^<]+)</a>", pos)
139                 if not title then break end -- this would not be normal behavior...
140                 _, _, year = string.find(table, "\((%d+)\)", pos)
141                 -- Add this title to the list
142                 count = count + 1
143                 _, _, imdbID = string.find(link, "/([^/]+)/$")
144                 title = replace_html_chars(title)
145                 titles[count] = { id = imdbID ; title = title ; year = year ; link = link }
146             end
147         end
148     end
149
150     for idx, title in ipairs(titles) do
151         list:add_value("[" .. title.id .. "] " .. title.title .. " (" .. title.year .. ")", idx)
152     end
153 end
154
155 function click_open()
156     selection = list:get_selection()
157     if not selection then return 1 end
158     if not html then
159         html = dlg:add_html("Loading IMDb page...", 1, 3, 4, 1)
160         -- userLink = dlg:add_label("", 1, 4, 5, 1)
161     end
162
163     dlg:del_widget(list)
164     dlg:del_widget(button_open)
165     list = nil
166     button_open = nil
167
168     local sel = nil
169     for idx, selectedItem in pairs(selection) do
170         sel = idx
171         break
172     end
173     imdbID = titles[sel].id
174     url = "http://www.imdb.org/title/" .. imdbID .. "/"
175
176     -- userLink:set_text("<a href=\"url\">" .. url .. "</a>")
177
178     local s, msg = vlc.stream(url)
179     if not s then
180         vlc.msg.warn(msg)
181         return
182     end
183
184     data = s:read(65000)
185
186     text = "<h1>" .. titles[sel].title .. " (" .. titles[sel].year .. ")</h1>"
187     text = text .. "<h2>Overview</h2><table>"
188
189     -- Director
190     local director = nil
191     _, nextIdx, _ = string.find(data, "<div id=\"director-info\"", 1, true)
192     if nextIdx then
193         _, _, director = string.find(data, "<a href[^>]+>([%w%s]+)</a>", nextIdx)
194     end
195     if not director then
196         director = "(Unknown)"
197     end
198     text = text .. "<tr><td><b>Director</b></td><td>" .. director .. "</td></tr>"
199
200     -- Main genres
201     local genres = "<tr><td><b>Genres</b></td>"
202     local first = true
203     for genre, _ in string.gmatch(data, "/Sections/Genres/(%w+)/\">") do
204         if first then
205             genres = genres .. "<td>" .. genre .. "</td></tr>"
206         else
207             genres = genres .. "<tr><td /><td>" .. genre .. "</td></tr>"
208         end
209         first = false
210     end
211     text = text .. genres
212
213     -- List main actors
214     local actors = "<tr><td><b>Cast</b></td>"
215     first = true
216     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
217         if not first then
218             actors = actors .. "<tr><td />"
219         end
220         actors = actors .. "<td>" .. nm .. "</td><td><i>" .. char .. "</i></td></tr>"
221         first = false
222     end
223     text = text .. actors .. "</table>"
224
225     text = text .. "<h2>Plot Summary</h2>"
226     s, msg = vlc.stream(url .. "plotsummary")
227     if not s then
228         vlc.msg.warn(msg)
229         return
230     end
231     data = s:read(65000)
232
233     -- We read only the first summary
234     _, _, summary = string.find(data, "<p class=\"plotpar\">([^<]+)")
235     if not summary then
236         summary = "(Unknown)"
237     end
238     text = text .. "<p>" .. summary .. "</p>"
239     text = text .. "<p><h2>Source IMDb</h2><a href=\"" .. url .. "\">" .. url .. "</a></p>"
240
241     html:set_text(text)
242 end
243
244 -- Convert some HTML characters into UTF8
245 function replace_html_chars(txt)
246     if not txt then return nil end
247     -- return vlc.strings.resolve_xml_special_chars(txt)
248     for num in string.gmatch(txt, "&#x(%x+);") do
249         -- Convert to decimal (any better way?)
250         dec = 0
251         for c in string.gmatch(num, "%x") do
252             cc = string.byte(c) - string.byte("0")
253             if (cc >= 10 or cc < 0) then
254                 cc = string.byte(string.lower(c)) - string.byte("a") + 10
255             end
256             dec = dec * 16 + cc
257         end
258         txt = string.gsub(txt, "&#x" .. num .. ";", string.char(dec))
259     end
260     return txt
261 end
262