git-svn-id: svn://euphorik.ch/pompage@24 02bbb61a-6d21-0410-aba0-cb053bdfd66a
[pompage.git] / src / film.rb
1 require 'rexml/document'
2 require 'net/http'
3 require 'thread'
4 require 'thwait'
5 require 'cgi'
6
7 require 'pays.rb'
8 require 'genre.rb'
9 require 'personne.rb'
10
11 require 'constantes.rb'
12
13 class String
14 def virerBalisesHTML
15 return self.gsub(/<(.*?)>/, '')
16 end
17 end
18
19 class Film
20 attr_accessor :id, :titre, :fichiers, :annee, :realisateurs, :acteurs, :pays, :duree, :critiquePresse, :critiqueSpectateur, :genres, :synopsis, :budget
21
22 # repertoire de base, par exemple C:/Divx/
23 @@repBase = ''
24
25 # Les films indexés par leur titre
26 @@films = {}
27
28 # Les films indexés par leur nom de fichier, deux fichiers différents peuvent pointer sur le même film
29 @@filmsFichier = {}
30
31 # Les films qui ont plusieurs réponses lors de la recherche, traités à la fin
32 @@filmsPlusieursReponses = []
33
34 @@threadsWait = ThreadsWait::new
35 @@nbConn = 0
36
37 # le prochain id disponible
38 @@idDisponible = 1
39
40 # retourne un nouvel id, utilisé lors de la création d'un nouveau film
41 def Film::getNewId
42 id = @@idDisponible
43 @@idDisponible += 1
44 return id
45 end
46
47 # Lit un repertoire de manière recursive
48 def Film::litRepertoire(r)
49 @@repBase = r
50 repPrecedant = Dir::getwd
51 Dir::chdir(r)
52
53 Film::litRepertoireR('.')
54
55 # on attends que les threads se terminent
56 @@threadsWait.all_waits
57
58 # traite les films qui avaient plusieurs réponses lors de la recherche
59 # l'utilisateur doit faire un choix
60 i = 1
61 @@filmsPlusieursReponses.each{|f|
62 puts
63 puts "Plop, ya un conflit #{i} / #{@@filmsPlusieursReponses.length} :"
64 if f.reglerConflitPlusieursReponses
65 Film::ajouterFilm(f)
66 end
67 i += 1
68 }
69
70 Dir::chdir(repPrecedant)
71 end
72
73 # Charge les films contenus dans un fichier XML.
74 def Film::loadFilmsXml(xmlFile)
75 # si le fichier n'existe pas il n'y a rien à charger
76 if !File.exists?(xmlFile)
77 return
78 end
79
80 racine = REXML::Document::new(File::new(xmlFile)).root
81 racine.each_element{|e|
82 id = e.attribute('id').to_s.to_i
83
84 if id > @@idDisponible
85 @@idDisponible = id + 1
86 end
87
88 titre = e.get_elements('titre')[0].get_text
89
90 fichiers = e.get_elements('fichiers')[0].get_elements('fichier')
91 annee = e.get_elements('annee')[0].get_text
92 duree = e.get_elements('duree')[0].get_text
93 critiquePresse = e.get_elements('critiquePresse')[0].get_text
94 critiqueSpectateur = e.get_elements('critiqueSpectateur')[0].get_text
95 synopsis = e.get_elements('synopsis')[0].get_text
96 budget = e.get_elements('budget')[0].get_text
97 realisateurs = e.get_elements('realisateurs')[0].get_elements('realisateur')
98 acteurs = e.get_elements('acteurs')[0].get_elements('acteur')
99 pays = e.get_elements('lespays')[0].get_elements('pays')
100 genres = e.get_elements('genres')[0].get_elements('genre')
101
102 film = Film::new(fichiers[0].get_text.value)
103
104 film.titre = titre.value unless titre.nil?
105 film.id = id
106 fichiers.each{|e|
107 film.addFichier(e.get_text.value)
108 @@filmsFichier[e.get_text.value] = film
109 }
110 film.annee = annee.value unless annee.nil?
111 acteurs.each{|e|
112 film.acteurs << Personne::ajouter(e.get_text.value)
113 }
114 pays.each{|e|
115 film.pays << Pays::ajouter(e.get_text.value)
116 }
117 film.duree = duree.value unless duree.nil?
118 film.critiquePresse = critiquePresse.value unless critiquePresse.nil?
119 film.critiqueSpectateur = critiqueSpectateur.value unless critiqueSpectateur.nil?
120 genres.each{|e|
121 film.genres << Genre::ajouter(e.get_text.value) if e.get_text != nil
122 }
123 film.synopsis = synopsis.value unless synopsis.nil?
124 film.budget = budget.value unless budget.nil?
125 @@films[film.titre] = film
126 }
127 end
128
129 # Renvoie tous les films sous la forme d'un document XML.
130 def Film::getFilmsXml
131 # le document
132 docXml = REXML::Document::new
133 docXml.xml_decl().encoding = "UTF-8" # normalement UTF-8
134 docXml.xml_decl().dowrite
135
136 # la racine du document
137 racine = REXML::Element::new('filmographie')
138 docXml.add(racine)
139 pi = REXML::Instruction.new("xml-stylesheet", "type=\"text/xsl\" href=\"../xsl/yopyop.xsl\"")
140 racine.previous_sibling = pi
141
142 # on ajoute chaque film à la racine
143 @@films.each{|nom, f|
144 racine.add(f.getXml)
145 }
146
147 # revoie le document
148 docXml
149 end
150
151 private
152
153 def Film::filmsFactory(fichier)
154 Film::new(fichier).loadData
155 end
156
157 def Film::litRepertoireR(r)
158 Dir::foreach(r){|f|
159 next if f[0,1] == '.'
160 fichier = (r == '.' ? '' : r + "/") + f
161 if File::directory?(fichier)
162 litRepertoireR(fichier)
163 else
164
165 # vérification de l'extension
166 /^.*?\.(.{3,4})$/ =~ fichier
167 if !FILMS_EXTENSIONS.include?($1)
168 next
169 end
170
171 fichier = CGI::escapeHTML(fichier.unpack("C*").pack("U*"))
172
173 # on skip si le film est déjàa dans la BD
174 if film = @@filmsFichier[fichier]
175 puts "[i] Already exists in DB : #{film.titre} (#{fichier})"
176 next
177 end
178
179 #p fichier
180
181 film = nil
182
183 if @@nbConn >= NB_CONN_MAX
184 @@threadsWait.next_wait
185 end
186
187 @@nbConn += 1
188 @@threadsWait.join_nowait(
189 Thread::new{
190 begin
191 film = Film::filmsFactory(fichier)
192 unless film.nil? # le film a été correctement construit
193 Film::ajouterFilm(film)
194 end
195 rescue Exception => e
196 puts e.message
197 puts e.backtrace
198 end
199 @@nbConn -= 1
200 }
201 )
202 end
203 }
204 end
205
206 def Film::ajouterFilm(film)
207 if film.plusieursReponses?
208 @@filmsPlusieursReponses << film
209 return
210 end
211
212 # le film existe déjà
213 if @@films.has_key?(film.titre)
214 # le fichier n'est pas connu -> nième partie d'un film
215 if !@@filmsFichier.has_key?(film.fichiers[0])
216 puts "[i] movie #{film.titre} has a another file part : #{film.fichiers[0]}"
217 @@films[film.titre].addFichier(film.fichiers[0])
218 @@filmsFichier[film.fichiers[0]] = @@films[film.titre]
219 else
220 puts "[!] Duplicate movie : #{film.titre} (#{film.fichiers[0]})"
221 end
222 else
223 puts "[i] movie added : #{film.titre} (#{film.fichiers[0]})"
224 @@films[film.titre] = film
225 @@filmsFichier[film.fichiers[0]] = film
226 end
227 end
228
229
230 def initialize(fichier)
231 @fichiers = [fichier]
232
233 @id = 0
234 @titre = ''
235 @annee = nil
236 @realisateurs = []
237 @acteurs = []
238 @pays = []
239 @duree = nil
240 @critiquePresse = nil
241 @critiqueSpectateur = nil
242 @genres = []
243 @synopsis = nil
244 @budget = nil
245 @budgetUnite = 'euro'
246 @url
247
248 @aPlusieursReponses = false
249 # mémorise les tuples {nom => id} dans le cas ou il y a plusieurs réponses
250 @idsAllocine = {}
251 end
252
253 public
254
255 def plusieursReponses?
256 return @aPlusieursReponses
257 end
258
259 # demande à l'utilisateur de faire un choix
260 # ret : true si le conflit à été résolu sinon false
261 def reglerConflitPlusieursReponses
262
263 @aPlusieursReponses = false # pour faire les choses bien
264
265 puts @fichiers[0]
266 puts "Fais ton choix jeune padawan (un caractère et pas plus)"
267 tabNoms = @idsAllocine.keys
268 choix = 1
269 loop do
270 i = 1
271 tabNoms.each{|n|
272 puts "#{i}. #{n}"
273 i += 1
274 }
275 puts "A. Passer et l'ajouter"
276 puts "B. Ignorer"
277 choix = STDIN.gets
278
279 if /A/i =~ choix
280 return true
281 elsif /B/i =~ choix
282 return false
283 end
284
285 choix = choix.to_i
286 if choix > 0 && choix <= tabNoms.length
287 break;
288 else
289 puts
290 puts "Choix pas bon !!"
291 end
292 end
293
294 loadDepuisIdAllocine(@idsAllocine[tabNoms[choix-1]])
295
296 return true
297 end
298
299 def addFichier(fichier)
300 if !@fichiers.include?(fichier)
301 @fichiers << fichier
302 end
303 end
304
305 # Charge les informations du films à partir d'allocine.fr
306 # ret [Film]
307 def loadData
308 unless LOAD_DATA
309 @titre = @fichiers[0]
310 return self
311 end
312
313 @id = Film::getNewId
314
315 connexionHttp = Net::HTTP::new('www.allocine.fr')
316
317 #extrait le nom à partir du nom du fichier
318 /^.*?([^\/]*?)\.(.{3,4})$/ =~ @fichiers[0]
319 #remplace undescores et points par des espaces
320 titre = $1.gsub(/[_\.]/, ' ')
321 #remplace les suites d'espaces par un seul
322 titre.gsub!(/ {2,}/,' ')
323 titre.gsub!(/\[.*?\]/,'')
324 titre.gsub!(/\(.*?\)/,'')
325 titre.gsub!(/\{.*?\}/,'')
326 #vire les espaces au début et à la fin
327 titre.strip!
328
329 @titre = titre.dup
330
331 donneesHtml = nil
332 begin
333 begin
334 reponse, donneesHtml = connexionHttp.get("/recherche/?motcle=#{CGI::escape(titre.unpack("U*").pack("C*"))}")
335 rescue Exception => e
336 p e
337 puts "[!] Connexion lost, retry.."
338 retry
339 end
340
341 #convertit le code latin-1 en UTF8
342 donneesHtml = donneesHtml.unpack("C*").pack("U*")
343
344 #si pas trouvé alors on enlève un mot à la fin
345 if /.*?Pas de résultats.*?/ =~ donneesHtml || ! donneesHtml.include?("<h3><b>Films <h4>")
346 /(.*?)[^ ]+?$/ =~ titre.strip
347 titre = $1
348 titre.strip!
349 else
350 break;
351 end
352 end while not titre.nil? and not titre.empty?
353
354 unless titre.nil? or titre.empty?
355
356 #/<a href="\/film\/fichefilm_gen_cfilm=(\d+)\.html" class="link1">/ =~ donneesHtml
357 #r = donneesHtml.scan(/<a href="\/film\/fichefilm_gen_cfilm=(\d+)\.html" class="link1">(.*?)<\/a>/)
358 r = donneesHtml.scan(/<a href="\/film\/fichefilm_gen_cfilm=(\d+)\.html" class="link1">(.*?)<\/a>(?:<\/h4><h5 style="color: #666666">&nbsp;(.*?)<\/h5>){0,1}/)
359
360 if r.length > 1
361 @aPlusieursReponses = true
362 r.each{|f|
363 @idsAllocine[f[1].virerBalisesHTML + (f[2] != nil ? " " + f[2].virerBalisesHTML : "")] = f[0]
364 }
365 elsif r.length == 1
366 loadDepuisIdAllocine(r[0][0], connexionHttp)
367 else
368 puts "[!] Movie not found : #{@titre} (#{@fichier})"
369 end
370 end
371 self
372 end
373
374 private
375 def loadDepuisIdAllocine(id, connexionHttp = nil)
376 if (connexionHttp == nil)
377 connexionHttp = Net::HTTP::new('www.allocine.fr')
378 end
379
380 r, ficheHtml = connexionHttp.get("/film/fichefilm_gen_cfilm=#{id}.html")
381
382 #convertit le code latin-1 en UTF8
383 ficheHtml = ficheHtml.unpack("C*").pack("U*")
384
385 #url
386 @url = "http://www.allocine.fr/film/fichefilm_gen_cfilm=#{id}.html"
387
388 # Titre
389 /<title>(.*?)<\/title>/ =~ ficheHtml
390 @titre = $1 unless $1.nil?
391
392 puts "Movie found : #{@titre} (#{@fichiers[0]})"
393
394 # Année
395 /<h4>Année de production : (\d+)<\/h4>/ =~ ficheHtml
396 @annee = $1 unless $1.nil?
397
398 # Réalisateurs
399 /<h4>Réalisé par(.*?)<\/h4>/ =~ ficheHtml
400 $1.scan(/<a class="link1" href=".*?">(.*?)<\/a>/m){|a|
401 @realisateurs << Personne::ajouter(a[0]) unless a[0].nil?
402 } unless $1.nil?
403
404 # Acteurs
405 /<h4>Avec(.*?)<\/h4>/ =~ ficheHtml
406 $1.scan(/<a class="link1" href="\/personne\/fichepersonne_gen_cpersonne=\d+\.html">(.+?)<\/a>/m){|a|
407 @acteurs << Personne::ajouter(a[0]) unless a[0].nil?
408 } unless $1.nil?
409
410 # Pays
411 /<h4>Film (.*?)\.&nbsp;<\/h4>/ =~ ficheHtml
412 $1.split(',').each{|pays|
413 @pays << Pays::ajouter(pays) unless pays.nil?
414 } unless $1.nil?
415
416 # Duree (capture des heures et des minutes séparement vue que c'est le bordel sur allocine
417 /<h4>Durée :(?:.*?)(\d+)h/ =~ ficheHtml
418 heure = $1.nil? ? 0 : $1.to_i
419
420 /<h4>Durée :(?:.*?)(\d+)min/ =~ ficheHtml
421 min = $1.nil? ? 0 : $1.to_i
422
423 @duree = heure * 60 + min
424
425 # Critiques presse et spectateur
426 /Presse.*etoile_([012345]).*Spectateurs.*etoile_([012345])"/m =~ ficheHtml
427 @critiquePresse = $1 unless $1.nil?
428 @critiqueSpectateur = $2 unless $2.nil?
429
430 # Genre
431 /<h4>Genre : (.*?)<\/h4>/ =~ ficheHtml
432 $1.scan(/<a href="\/film\/alaffiche_genre_gen_genre=.*?" class="link1">(.+?)<\/a>/m){|g|
433 @genres << Genre::ajouter(g[0]) unless g[0].nil?
434 } unless $1.nil?
435
436 # Synopsis
437 /Synopsis.*?<h4>(.+?)<\/h4>/m =~ ficheHtml
438 @synopsis = $1 unless $1.nil?
439
440 # Budget
441 /Budget<\/b> : (.+?) millions d'euros<\/h4>/ =~ ficheHtml
442 @budget = $1 unless $1.nil?
443 end
444
445 public
446
447 # Renvoie un film sous la forme d'un élément XML de type REXML
448 def getXml
449
450 racine = REXML::Element::new('film')
451 racine.add_attribute('id', @id.to_s)
452
453 fichiers = REXML::Element::new('fichiers')
454 @fichiers.each{|f|
455 fichiers.add(REXML::Element::new('fichier').add_text(f))
456 }
457 racine.add(fichiers)
458
459 racine.add(REXML::Element::new('titre').add_text(@titre))
460 racine.add(REXML::Element::new('annee').add_text(@annee))
461
462 realisateurs = REXML::Element::new('realisateurs')
463 @realisateurs.each{|r|
464 realisateurs.add(REXML::Element::new('realisateur').add_text(r.nom))
465 }
466 racine.add(realisateurs)
467
468 acteurs = REXML::Element::new('acteurs')
469 @acteurs.each{|a|
470 acteurs.add(REXML::Element::new('acteur').add_text(a.nom))
471 }
472 racine.add(acteurs)
473
474 lespays = REXML::Element::new('lespays')
475 @pays.each{|p|
476 lespays.add(REXML::Element::new('pays').add_text(p.nom))
477 }
478 racine.add(lespays)
479
480 racine.add(REXML::Element::new('duree').add_text(@duree.to_s))
481
482 racine.add(REXML::Element::new('critiquePresse').add_text(@critiquePresse))
483 racine.add(REXML::Element::new('critiqueSpectateur').add_text(@critiqueSpectateur))
484
485 genres = REXML::Element::new('genres')
486 @genres.each{|g|
487 genres.add(REXML::Element::new('genre').add_text(g.nom))
488 }
489 racine.add(genres)
490
491 racine.add(REXML::Element::new('synopsis').add_text(@synopsis))
492 budgetElement = REXML::Element::new('budget')
493 budgetElement.add_text(@budget)
494 budgetElement.add_attribute('unite', @budgetUnite)
495 racine.add(budgetElement)
496
497 racine.add(REXML::Element::new('url').add_text(@url))
498
499 racine
500 end
501 end
502