lyrics 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +17 -0
  5. data/Rakefile +45 -0
  6. data/VERSION +1 -0
  7. data/bin/lyrics +66 -0
  8. data/lib/lyrics.rb +4 -0
  9. data/lib/lyrics/cli/application.rb +99 -0
  10. data/lib/lyrics/cli/optionsparser.rb +228 -0
  11. data/lib/lyrics/cli/pluginadapter.rb +56 -0
  12. data/lib/lyrics/cli/plugins.rb +79 -0
  13. data/lib/lyrics/cli/wikipluginadapter.rb +139 -0
  14. data/lib/lyrics/i18n/README +1 -0
  15. data/lib/lyrics/i18n/en.rb +181 -0
  16. data/lib/lyrics/i18n/es.rb +181 -0
  17. data/lib/lyrics/i18n/i18n.rb +126 -0
  18. data/lib/lyrics/i18n/sk.rb +174 -0
  19. data/lib/lyrics/itrans/COPYRIGHT +31 -0
  20. data/lib/lyrics/itrans/itrans +0 -0
  21. data/lib/lyrics/itrans/itrans.txt +8 -0
  22. data/lib/lyrics/itrans/lyric.txt +23 -0
  23. data/lib/lyrics/itrans/udvng.ifm +206 -0
  24. data/lib/lyrics/lyrics.rb +567 -0
  25. data/lib/lyrics/lyrics_AZLyrics.rb +113 -0
  26. data/lib/lyrics/lyrics_DarkLyrics.rb +124 -0
  27. data/lib/lyrics/lyrics_Giitaayan.rb +124 -0
  28. data/lib/lyrics/lyrics_Jamendo.rb +166 -0
  29. data/lib/lyrics/lyrics_LeosLyrics.rb +142 -0
  30. data/lib/lyrics/lyrics_LoudSongs.rb +135 -0
  31. data/lib/lyrics/lyrics_LyricWiki.rb +328 -0
  32. data/lib/lyrics/lyrics_LyricsDownload.rb +118 -0
  33. data/lib/lyrics/lyrics_LyricsMania.rb +141 -0
  34. data/lib/lyrics/lyrics_Lyriki.rb +286 -0
  35. data/lib/lyrics/lyrics_SeekLyrics.rb +108 -0
  36. data/lib/lyrics/lyrics_Sing365.rb +103 -0
  37. data/lib/lyrics/lyrics_TerraLetras.rb +126 -0
  38. data/lib/lyrics/mediawikilyrics.rb +1417 -0
  39. data/lib/lyrics/utils/formdata.rb +56 -0
  40. data/lib/lyrics/utils/htmlentities.rb +291 -0
  41. data/lib/lyrics/utils/http.rb +198 -0
  42. data/lib/lyrics/utils/itrans.rb +160 -0
  43. data/lib/lyrics/utils/logger.rb +123 -0
  44. data/lib/lyrics/utils/strings.rb +378 -0
  45. data/lib/lyrics/utils/xmlhash.rb +111 -0
  46. data/lyrics.gemspec +98 -0
  47. data/spec/lyrics_spec.rb +7 -0
  48. data/spec/spec.opts +1 -0
  49. data/spec/spec_helper.rb +9 -0
  50. metadata +137 -0
@@ -0,0 +1,31 @@
1
+ /*
2
+ * ITRANS software
3
+ *==========================================================================
4
+ * Copyright 1991--2001 Avinash Chopde, All Rights Reserved.
5
+ *
6
+ * Permission to use, copy, modify and distribute this software and its
7
+ * documentation for any purpose is hereby granted without fee, provided that
8
+ * the above copyright notice appear in all copies and that both that
9
+ * copyright notice and this permission notice appear in supporting
10
+ * documentation, and that the name of Avinash Chopde not be used in
11
+ * advertising or publicity pertaining to distribution of the software
12
+ * without specific, written prior permission.
13
+ * Avinash Chopde makes no representations about the suitability of this
14
+ * software for any purpose.
15
+ * It is provided "as is" without express or implied warranty.
16
+ *
17
+ * AVINASH CHOPDE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
18
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
19
+ * IN NO EVENT SHALL AVINASH CHOPDE BE LIABLE FOR ANY SPECIAL, INDIRECT OR
20
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
22
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
23
+ * OF THIS SOFTWARE.
24
+ *
25
+ * Author: Avinash Chopde
26
+ * e-mail: avinash@acm.org
27
+ * home page: http://www.aczone.com/itrans/
28
+ *
29
+ If this package contains any other copyrighted material, the appropriate
30
+ notice has been included in such units.
31
+ */
Binary file
@@ -0,0 +1,8 @@
1
+ #indianifm=udvng.ifm
2
+
3
+ #indian
4
+ मिलती ह नज़रो स जब नज़र
5
+ होता ह दिल प असर
6
+ हा मिल जाए दिल स अगर दिल
7
+ तो मिलत ह दो हमसफ़र
8
+ #endindian
@@ -0,0 +1,23 @@
1
+ #indianifm=udvng.ifm
2
+ #indian
3
+ %
4
+ ##baby baby baby baby baby baby##
5
+ shaa naa naa
6
+ sun baabaa sun baabaa dil kaa kyaa kahanaa
7
+ shaa naa naa \threedots
8
+
9
+ ##reason## hai kuchh gul khilaa naa
10
+ ##season## bhii hai aashiqaanaa
11
+ haay aise me.n dil diivaanaa bas me.n kahaa.N
12
+ ##take it easy, baby##
13
+ aavaaraa cha.nchal havaa_o.n pe chhaa_ii hai.n madahoshiyaa.N
14
+ aise me.n ko_ii jo kho jaa_e ho jaa_e.n dil kii choriyaa.n
15
+ shaa naa naa \threedots
16
+
17
+ milatii hai.n nazaro.n se jab nazare.n
18
+ hotaa hai dil pe asar
19
+ haa.n mil jaa_e dil se agar dil
20
+ to milate hai.n do hamasafar
21
+ shaa naa naa \threedots
22
+ %
23
+ #endindian
@@ -0,0 +1,206 @@
1
+ Comment * ITRANS IFM file for Unicode Output
2
+ Comment *===================================================================
3
+ Comment * Copyright 2001 Avinash Chopde, All Rights Reserved.
4
+ Comment *
5
+ Comment * Author: Avinash Chopde <avinash@acm.org> http://www.aczone.com/
6
+ Comment ===================================================================
7
+ % ------------- Instructions for Creating ITRANS IFM files for Unicode
8
+ % The following page has Unicode ranges for all languages, and
9
+ % includes both Decimal and Hexadecimal values:
10
+ % http://www.hclrss.demon.co.uk/unicode/devanagari.html
11
+ % For ITRANS tables, visit the online documents at:
12
+ % http://www.aczone.com/itrans/#onlinedocs
13
+ %
14
+ % The PCC <charcode> ... statements below accept either Decimal or
15
+ % Hexadecimal, so both of these are identical:
16
+ % PCC 2309 0 0 ;
17
+ % is same as:
18
+ % PCC 0x0905 0 0 ;
19
+ % [note: hex numbers must be preceed with "0x", decimal numbers must not
20
+ % have leading 0's - leading 0's imply that the number is octal]
21
+ % -------------
22
+ Comment ===================================================================
23
+
24
+ itrans: StartINDIAN
25
+ itrans: TEXT_UNICODE sanskrit
26
+ itrans: DEFAULTVOWEL a ;
27
+
28
+ % These are in Unicode, but not used by ITRANS:
29
+ % 2317 090D DEVANAGARI LETTER CANDRA E
30
+ % 2318 090E DEVANAGARI LETTER SHORT E
31
+ % 2321 0911 DEVANAGARI LETTER CANDRA O
32
+ % 2322 0912 DEVANAGARI LETTER SHORT O
33
+ % 2356 0934 DEVANAGARI LETTER LLLA (lda-dot)
34
+ % danda, double danda, nukta, udatta, anudatta, abbreviation sign
35
+
36
+ itrans: Comment The Vowels - Independent Vowels in Unicode
37
+
38
+ itrans: CC a 1 ; PCC 2309 0 0 ;
39
+ itrans: CC aa 1 ; PCC 2310 0 0 ;
40
+ itrans: CC i 1 ; PCC 2311 0 0 ;
41
+ itrans: CC ii 1 ; PCC 2312 0 0 ;
42
+ itrans: CC u 1 ; PCC 2313 0 0 ;
43
+ itrans: CC uu 1 ; PCC 2314 0 0 ;
44
+ itrans: CC ri 1 ; PCC 2315 0 0 ;
45
+ itrans: CC rii 1 ; PCC 2400 0 0 ;
46
+ itrans: CC li 1 ; PCC 2316 0 0 ;
47
+ itrans: CC lii 1 ; PCC 2401 0 0 ;
48
+ itrans: CC ay 1 ; PCC 2319 0 0 ;
49
+ % itrans: CC aay 1 ; % Telugu, etc only ( E ) ?
50
+ itrans: CC aay 1 ; PCC 2319 0 0 ;
51
+ itrans: CC ai 1 ; PCC 2320 0 0 ;
52
+ itrans: CC o 1 ; PCC 2323 0 0 ;
53
+ % itrans: CC oo 1 ; % Telugu, etc only ( O ) ?
54
+ itrans: CC oo 1 ; PCC 2323 0 0 ;
55
+ itrans: CC au 1 ; PCC 2324 0 0 ;
56
+ itrans: CC am 2 ; PCC 2309 0 0 ; PCC 2306 0 0 ;
57
+ itrans: CC aha 1 ; PCC 2307 0 0 ; % just visarga
58
+
59
+ itrans: Comment The Template - use Dependent Vowels as defined in Unicode
60
+
61
+ itrans: CCADD tmplA ;
62
+ itrans: CC tmplA-half 2 ; PCC implicit 0 0 ; PCC 2381 0 0 ;
63
+ itrans: CC tmplA-conjunct 2 ; PCC implicit 0 0 ; PCC 2381 0 0 ;
64
+ itrans: CC tmplA-a 1 ; PCC implicit 0 0 ;
65
+ itrans: CC tmplA-aa 2 ; PCC implicit 0 0 ; PCC 2366 0 0 ;
66
+ itrans: CC tmplA-i 2 ; PCC implicit 0 0 ; PCC 2367 0 0 ;
67
+ itrans: CC tmplA-ii 2 ; PCC implicit 0 0 ; PCC 2368 0 0 ;
68
+ itrans: CC tmplA-u 2 ; PCC implicit 0 0 ; PCC 2369 0 0 ;
69
+ itrans: CC tmplA-uu 2 ; PCC implicit 0 0 ; PCC 2370 0 0 ;
70
+ itrans: CC tmplA-ri 2 ; PCC implicit 0 0 ; PCC 2371 0 0 ;
71
+ itrans: CC tmplA-rii 2 ; PCC implicit 0 0 ; PCC 2372 0 0 ;
72
+ itrans: CC tmplA-li 2 ; PCC implicit 0 0 ; PCC 2402 0 0 ;
73
+ itrans: CC tmplA-lii 2 ; PCC implicit 0 0 ; PCC 2403 0 0 ;
74
+ itrans: CC tmplA-ay 2 ; PCC implicit 0 0 ; PCC 2375 0 0 ;
75
+ itrans: CC tmplA-aay 2 ; PCC implicit 0 0 ; PCC 2375 0 0 ;
76
+ itrans: CC tmplA-ai 2 ; PCC implicit 0 0 ; PCC 2376 0 0 ;
77
+ itrans: CC tmplA-o 2 ; PCC implicit 0 0 ; PCC 2379 0 0 ;
78
+ itrans: CC tmplA-oo 2 ; PCC implicit 0 0 ; PCC 2379 0 0 ;
79
+ itrans: CC tmplA-au 2 ; PCC implicit 0 0 ; PCC 2380 0 0 ;
80
+ itrans: CC tmplA-am 2 ; PCC implicit 0 0 ; PCC 2306 0 0 ;
81
+ itrans: CC tmplA-aha 2 ; PCC implicit 0 0 ; PCC 2307 0 0 ;
82
+
83
+ itrans: Comment The Consonants - in Unicode, all are full consonants
84
+
85
+ itrans: CCS ka tmplA ;
86
+ itrans: CC ka-implicit 1 ; PCC 2325 0 0 ;
87
+ itrans: CCS kadot ka ;
88
+ itrans: CC kadot-implicit 1 ; PCC 2392 0 0 ;
89
+ itrans: CCS kha tmplA ;
90
+ itrans: CC kha-implicit 1 ; PCC 2326 0 0 ;
91
+ itrans: CCS khadot kha ;
92
+ itrans: CC khadot-implicit 1 ; PCC 2393 0 0 ;
93
+ itrans: CCS ga tmplA ;
94
+ itrans: CC ga-implicit 1 ; PCC 2327 0 0 ;
95
+ itrans: CCS gadot ga ;
96
+ itrans: CC gadot-implicit 1 ; PCC 2394 0 0 ;
97
+ itrans: CCS gha tmplA ;
98
+ itrans: CC gha-implicit 1 ; PCC 2328 0 0 ;
99
+ itrans: CCS nga tmplA ;
100
+ itrans: CC nga-implicit 1 ; PCC 2329 0 0 ;
101
+ itrans: CCS cha tmplA ;
102
+ itrans: CC cha-implicit 1 ; PCC 2330 0 0 ;
103
+ itrans: CCS chha tmplA ;
104
+ itrans: CC chha-implicit 1 ; PCC 2331 0 0 ;
105
+ itrans: CCS ja tmplA ;
106
+ itrans: CC ja-implicit 1 ; PCC 2332 0 0 ;
107
+ itrans: CCS jadot ja ;
108
+ itrans: CC jadot-implicit 1 ; PCC 2395 0 0 ;
109
+ itrans: CCS jha tmplA ;
110
+ itrans: CC jha-implicit 1 ; PCC 2333 0 0 ;
111
+ itrans: CCS jnh tmplA ;
112
+ itrans: CC jnh-implicit 1 ; PCC 2334 0 0 ;
113
+ itrans: CCS tta tmplA ;
114
+ itrans: CC tta-implicit 1 ; PCC 2335 0 0 ;
115
+ itrans: CCS ttha tmplA ;
116
+ itrans: CC ttha-implicit 1 ; PCC 2336 0 0 ;
117
+ itrans: CCS dda tmplA ;
118
+ itrans: CC dda-implicit 1 ; PCC 2337 0 0 ;
119
+ itrans: CCS ddadot tmplA ;
120
+ itrans: CC ddadot-implicit 1 ; PCC 2396 0 0 ;
121
+ itrans: CCS ddha tmplA ;
122
+ itrans: CC ddha-implicit 1 ; PCC 2338 0 0 ;
123
+ itrans: CCS ddhadot ddadot ;
124
+ itrans: CC ddhadot-implicit 1 ; PCC 2397 0 0 ;
125
+ itrans: CCS nna tmplA ;
126
+ itrans: CC nna-implicit 1 ; PCC 2339 0 0 ;
127
+ itrans: CCS ta tmplA ;
128
+ itrans: CC ta-implicit 1 ; PCC 2340 0 0 ;
129
+ itrans: CCS tha tmplA ;
130
+ itrans: CC tha-implicit 1 ; PCC 2341 0 0 ;
131
+ itrans: CCS da tmplA;
132
+ itrans: CC da-implicit 1 ; PCC 2342 0 0 ;
133
+ itrans: CCS dha tmplA ;
134
+ itrans: CC dha-implicit 1 ; PCC 2343 0 0 ;
135
+ itrans: CCS na tmplA ;
136
+ itrans: CC na-implicit 1 ; PCC 2344 0 0 ;
137
+ itrans: CCS nnx tmplA ; % Tamil, etc only? ( ^n )
138
+ itrans: CC nnx-implicit 1 ; PCC 2345 0 0 ; % Devanagari: used as na-dot NNNA
139
+ itrans: CCS pa tmplA ;
140
+ itrans: CC pa-implicit 1 ; PCC 2346 0 0 ;
141
+ itrans: CCS pha tmplA ;
142
+ itrans: CC pha-implicit 1 ; PCC 2347 0 0 ;
143
+ itrans: CCS phadot pha ;
144
+ itrans: CC phadot-implicit 1 ; PCC 2398 0 0 ;
145
+ itrans: CCS ba tmplA ;
146
+ itrans: CC ba-implicit 1 ; PCC 2348 0 0 ;
147
+ itrans: CCS bha tmplA ;
148
+ itrans: CC bha-implicit 1 ; PCC 2349 0 0 ;
149
+ itrans: CCS ma tmplA ;
150
+ itrans: CC ma-implicit 1 ; PCC 2350 0 0 ;
151
+ itrans: CCS ya tmplA ;
152
+ itrans: CC ya-implicit 1 ; PCC 2351 0 0 ;
153
+ itrans: CCS yya tmplA ; % Devanagari: used as ya-dot
154
+ itrans: CC yya-implicit 1 ; PCC 2399 0 0 ;
155
+ itrans: CCS ra tmplA ;
156
+ itrans: CC ra-implicit 1 ; PCC 2352 0 0 ;
157
+ itrans: CCS rra tmplA ;
158
+ itrans: CC rra-implicit 1 ; PCC 2353 0 0 ; % RRA ra-dot
159
+ itrans: CCS la tmplA ;
160
+ itrans: CC la-implicit 1 ; PCC 2354 0 0 ;
161
+ itrans: CCS va tmplA ;
162
+ itrans: CC va-implicit 1 ; PCC 2357 0 0 ;
163
+ itrans: CCS sha tmplA ;
164
+ itrans: CC sha-implicit 1 ; PCC 2358 0 0 ;
165
+ itrans: CCS shha tmplA ;
166
+ itrans: CC shha-implicit 1 ; PCC 2359 0 0 ;
167
+ itrans: CCS sa tmplA ;
168
+ itrans: CC sa-implicit 1 ; PCC 2360 0 0 ;
169
+ itrans: CCS ha tmplA ;
170
+ itrans: CC ha-implicit 1 ; PCC 2361 0 0 ;
171
+ itrans: CCS lda tmplA ;
172
+ itrans: CC lda-implicit 1 ; PCC 2355 0 0 ;
173
+ itrans: CCS ksha tmplA ; % ka-shha
174
+ itrans: CC ksha-implicit 3 ; PCC 2325 0 0 ; PCC 2381 0 0 ; PCC 2359 0 0 ;
175
+ itrans: CCS gya tmplA ; % ja-jnh Unicode JA-NYA
176
+ itrans: CC gya-implicit 3 ; PCC 2332 0 0 ; PCC 2381 0 0 ; PCC 2334 0 0 ;
177
+
178
+ itrans: Comment The Specials (Incomplete support in ITRANS)
179
+
180
+ itrans: CC anusvara-implicit 1 ; PCC 2306 0 0 ;
181
+ itrans: CC chandra-implicit 1 ; PCC 2373 0 0 ; % 2373=CANDRA E, 2374=SHORT E
182
+ itrans: CC chandrabindu-implicit 1 ; PCC 2305 0 0 ;
183
+ itrans: CC aum-implicit 1 ; PCC 2384 0 0 ;
184
+ itrans: CC viraam-implicit 1 ; PCC 2381 0 0 ;
185
+ itrans: CC avagraha-implicit 1 ; PCC 2365 0 0 ;
186
+ % itrans: CC sri-implicit 1 ; PCC 244 0 0 ; % Tamil only?
187
+
188
+ % rahalf ( ^r or .r ) is a hack that was used in ITRANS, for the
189
+ % half-chandra-ra superscript, it would be very tricky to have it supported
190
+ % here, the best option is to avoid rahalf in the input. Use normal ra instead,
191
+ % i.e, instead of ga.r use rga in the input.
192
+ itrans: CC rahalf-implicit 2 ; PCC 2352 0 0 ; PCC 2381 0 0 ; % Devanagari only?
193
+
194
+ itrans: Comment ----------- Digits
195
+ itrans: CC zero 1 ; PCC 2406 0 0 ;
196
+ itrans: CC one 1 ; PCC 2407 0 0 ;
197
+ itrans: CC two 1 ; PCC 2408 0 0 ;
198
+ itrans: CC three 1 ; PCC 2409 0 0 ;
199
+ itrans: CC four 1 ; PCC 2410 0 0 ;
200
+ itrans: CC five 1 ; PCC 2411 0 0 ;
201
+ itrans: CC six 1 ; PCC 2412 0 0 ;
202
+ itrans: CC seven 1 ; PCC 2413 0 0 ;
203
+ itrans: CC eight 1 ; PCC 2414 0 0 ;
204
+ itrans: CC nine 1 ; PCC 2415 0 0 ;
205
+
206
+ itrans: EndINDIAN
@@ -0,0 +1,567 @@
1
+ # Copyright (C) 2006-2008 by Sergio Pistone
2
+ # sergio_pistone@yahoo.com.ar
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the
16
+ # Free Software Foundation, Inc.,
17
+ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
+
19
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
20
+
21
+ require "utils/logger"
22
+ require "utils/strings"
23
+ require "utils/http"
24
+
25
+ $VERBOSE_LOG = false
26
+
27
+ def debug( message )
28
+ system( "kdialog", "--msgbox", "<qt>" + message.to_s().gsub( "\n", "<br />" ) + "</qt>" )
29
+ end
30
+
31
+ class Lyrics
32
+
33
+ class Request
34
+
35
+ attr_reader :artist, :title, :album, :year, :fetch_data
36
+ attr_writer :fetch_data
37
+
38
+ def initialize( artist, title, album=nil, year=nil )
39
+ raise "target artist and title values can't be nil" if ! artist || ! title
40
+ @artist = artist.clone().freeze()
41
+ @title = title.clone().freeze()
42
+ @album = album ? album.clone().freeze() : nil
43
+ @year = year ? year.to_s().strip().to_i() : nil
44
+ @fetch_data = nil
45
+ end
46
+
47
+ def to_s()
48
+ ret = " - request artist: #{@artist}\n - request title: #{@title}"
49
+ ret << "\n - request album: #{@album}" if @album
50
+ ret << "\n - request year: #{@year}" if @year
51
+ return ret
52
+ end
53
+
54
+ end
55
+
56
+ class Response
57
+
58
+ attr_reader :request, :artist, :title, :album, :year, :lyrics, :url, :custom_data #, :suggestions
59
+ attr_writer :url
60
+
61
+ def initialize( request )
62
+ raise "request can't be nil" if ! request
63
+ @request = request
64
+ @artist = nil
65
+ @title = nil
66
+ @album = nil
67
+ @year = nil
68
+ @lyrics = nil
69
+ @custom_data = {}
70
+ end
71
+
72
+ def to_s()
73
+ ret = ""
74
+ ret << "\n - response artist: #{@artist}" if @artist
75
+ ret << "\n - response title: #{@title}" if @title
76
+ ret << "\n - response album: #{@album}" if @album
77
+ ret << "\n - response year: #{@year}" if @year
78
+ if @custom_data.size > 0
79
+ ret << "\n - response custom data:"
80
+ @custom_data.each { |key, val| ret << "\n - #{key}: #{val}" }
81
+ end
82
+ return ret.size > 0 ? ret.slice( 1..-1 ) : ret
83
+ end
84
+
85
+ def each_modified!()
86
+ yield "artist", @artist if artist_modified?()
87
+ yield "title", @title if title_modified?()
88
+ yield "album", @album if album_modified?()
89
+ yield "year", @year if year_modified?()
90
+ end
91
+
92
+ def each_modified()
93
+ each_modified!() { |k, v| yield k, v.clone() }
94
+ end
95
+
96
+ def artist_modified?()
97
+ return @artist && @artist != request.artist
98
+ end
99
+
100
+ def artist=( artist )
101
+ @artist = artist ? artist.to_s().strip().freeze() : nil
102
+ end
103
+
104
+ def title_modified?()
105
+ return @title && @title != request.title
106
+ end
107
+
108
+ def title=( title )
109
+ @title = title ? title.to_s().strip().freeze() : nil
110
+ end
111
+
112
+ def album_modified?()
113
+ return @album && @album != request.album
114
+ end
115
+
116
+ def album=( album )
117
+ @album = album ? album.to_s().strip().freeze() : nil
118
+ end
119
+
120
+ def year_modified?()
121
+ return @year && @year != request.year
122
+ end
123
+
124
+ def year=( year )
125
+ if year
126
+ year = year.to_s().strip().to_i()
127
+ if year > 1900
128
+ @year = year
129
+ else
130
+ @year = nil
131
+ end
132
+ else
133
+ @year = nil
134
+ end
135
+ end
136
+
137
+ def lyrics=( lyrics )
138
+ @lyrics = Strings.decode_htmlentities!( lyrics.to_s() ).strip().freeze()
139
+ @lyrics = nil if @lyrics.empty?
140
+ end
141
+
142
+ def custom_data=( custom_data )
143
+ raise "custom_data value must be a hash" if ! custom_data.is_a?( Hash )
144
+ @custom_data = {}
145
+ custom_data.each() { |k, v| @custom_data[k] = v.is_a?( String ) ? v.clone().freeze() : v }
146
+ @custom_data.freeze()
147
+ end
148
+
149
+ end
150
+
151
+ class Suggestion
152
+
153
+ attr_reader :url, :artist, :title
154
+
155
+ def initialize( artist, title, url )
156
+ @artist = artist.clone().freeze()
157
+ @title = title.clone().freeze()
158
+ @url = url.clone.freeze()
159
+ end
160
+
161
+ def to_s()
162
+ return "art: #{artist} | tit: #{title} | url: #{url}"
163
+ end
164
+
165
+ end
166
+
167
+ class FetchPageData
168
+
169
+ attr_reader :url, :post_data, :headers_data
170
+
171
+ def initialize( url, post_data=nil, headers_data=nil )
172
+ @url = url.freeze()
173
+ if post_data && post_data.size > 0
174
+ @post_data = post_data.clone().freeze()
175
+ @post_data.each() do |key|
176
+ @post_data[key] = @post_data[key].clone().freeze() if @post_data[key]
177
+ end
178
+ else
179
+ @post_data = nil
180
+ end
181
+ if headers_data && headers_data.size > 0
182
+ @headers_data = headers_data.clone().freeze()
183
+ @headers_data.each() do |key|
184
+ @headers_data[key] = @headers_data[key].clone().freeze() if @headers_data[key]
185
+ end
186
+ else
187
+ @headers_data = nil
188
+ end
189
+ end
190
+
191
+ def to_s()
192
+ ret = " - fetch url: #{@url}"
193
+ if @post_data
194
+ ret << "\n - fetch post data:"
195
+ @post_data.each { |key, val| ret << "\n - #{key}: #{val}" }
196
+ end
197
+ if @headers_data
198
+ ret << "\n - fetch headers data:"
199
+ @headers_data.each { |key, val| ret << "\n - #{key}: #{val}" }
200
+ end
201
+ return ret
202
+ end
203
+
204
+ end
205
+
206
+ attr_reader :cleanup_lyrics
207
+ attr_writer :cleanup_lyrics
208
+
209
+ def initialize( cleanup_lyrics=true, logger=nil )
210
+ super()
211
+ @cleanup_lyrics = cleanup_lyrics
212
+ @logger = logger
213
+ @tabulation = nil
214
+ @skip_tabulation = false
215
+ end
216
+
217
+ def log?()
218
+ return @logger != nil
219
+ end
220
+
221
+ def verbose_log?()
222
+ return log? && $VERBOSE_LOG
223
+ end
224
+
225
+ def logger=( logger )
226
+ @logger = logger
227
+ end
228
+
229
+ def log( message, new_lines=1 )
230
+ @logger.log( message, new_lines ) if @logger
231
+ end
232
+
233
+ def increase_tabulation_level()
234
+ @logger.increase_tabulation_level() if @logger
235
+ end
236
+
237
+ def decrease_tabulation_level()
238
+ @logger.decrease_tabulation_level() if @logger
239
+ end
240
+
241
+ def site_host()
242
+ return self.class.site_host()
243
+ end
244
+
245
+ def site_name()
246
+ return self.class.site_name()
247
+ end
248
+
249
+ def Lyrics.build_song_add_url( request )
250
+ return nil
251
+ end
252
+
253
+ def build_song_add_url( request )
254
+ return self.class.build_song_add_url( request )
255
+ end
256
+
257
+ def Lyrics.known_url?( url )
258
+ return url.index( "http://#{site_host}" ) == 0
259
+ end
260
+
261
+ def known_url?( url )
262
+ return self.class.known_url?( url )
263
+ end
264
+
265
+ def notify( message )
266
+ puts "#{site_name()}: #{message}"
267
+ end
268
+
269
+ # Returns a FetchPageData instance
270
+ def build_lyrics_fetch_data( request )
271
+ return FetchPageData.new( nil )
272
+ end
273
+
274
+ # Returns the lyrics page body (or nil if it couldn't be retrieved) and the corresponding url (or nil)
275
+ def fetch_lyrics_page( fetch_data )
276
+
277
+ return nil, nil if ! fetch_data.url
278
+
279
+ log( "Fetching lyrics page... ", 0 )
280
+ if fetch_data.post_data
281
+ response, url = HTTP.fetch_page_post( fetch_data.url, fetch_data.post_data, fetch_data.headers_data )
282
+ else
283
+ response, url = HTTP.fetch_page_get( fetch_data.url, fetch_data.headers_data )
284
+ end
285
+
286
+ if response && response.body()
287
+ log( "OK" )
288
+ log( response.body(), 2 ) if verbose_log?
289
+ return response.body(), url
290
+ else
291
+ log( "ERROR" )
292
+ return nil, url
293
+ end
294
+
295
+ end
296
+
297
+ # NOTE: override for example if your using google feeling lucky to verify that
298
+ # the resulting page is not just some random (trashy) result.
299
+ # Returns false is page_body does not correspond to the requested lyrics
300
+ def lyrics_page_valid?( request, page_body, page_url )
301
+ return true
302
+ end
303
+
304
+ # NOTE: implement in subclasses
305
+ # Must fill the relevante response properties (lyrics, [artist, title, album, year, custom_data])
306
+ # def parse_lyrics( response, page_body )
307
+ # end
308
+
309
+ # Returns a Lyrics::Response object
310
+ def lyrics_direct_search( request )
311
+
312
+ fetch_data = build_lyrics_fetch_data( request )
313
+
314
+ log(
315
+ site_name().upcase() + "\n" +
316
+ "Attempting LYRICS DIRECT SEARCH...\n" +
317
+ request.to_s() + "\n" +
318
+ fetch_data.to_s()
319
+ )
320
+
321
+ page_body, page_url = fetch_lyrics_page( fetch_data )
322
+
323
+ response = Response.new( request )
324
+
325
+ if page_body
326
+ if lyrics_page_valid?( request, page_body, page_url )
327
+ log( "Parsing lyrics... ", 0 )
328
+ response.url = page_url
329
+ parse_lyrics( response, page_body )
330
+ log( response.lyrics ? "LYRICS FOUND" : "LYRICS NOT FOUND" )
331
+ log( response.to_s() )
332
+ log( " - parsed lyrics:\n[#{lyrics_data.lyrics}" ) if verbose_log?
333
+ response.lyrics = Strings.cleanup_lyrics( response.lyrics ) if response.lyrics && @cleanup_lyrics
334
+ else
335
+ log( "LYRICS PAGE IS INVALID" )
336
+ end
337
+ end
338
+
339
+ return response
340
+
341
+ end
342
+
343
+ # Returns a Lyrics::Response object
344
+ def lyrics_from_url( request, url )
345
+
346
+ response = Response.new( request )
347
+
348
+ if ! known_url?( url )
349
+ log( site_name().upcase() + "\nCan't handle received URL: " + url )
350
+ return response
351
+ end
352
+
353
+ fetch_data = build_lyrics_fetch_data( request )
354
+ fetch_data = FetchPageData.new( url, fetch_data.post_data, fetch_data.headers_data )
355
+
356
+ log(
357
+ site_name().upcase() + "\n" +
358
+ "Retrieving LYRICS FROM URL...\n" +
359
+ request.to_s() + "\n" +
360
+ fetch_data.to_s()
361
+ )
362
+
363
+ page_body, url = fetch_lyrics_page( fetch_data )
364
+
365
+ if page_body
366
+ log( "Parsing lyrics... ", 0 )
367
+ response.url = url
368
+ parse_lyrics( response, page_body )
369
+ log( response.lyrics ? "LYRICS FOUND" : "LYRICS NOT FOUND" )
370
+ log( response.to_s() )
371
+ log( " - parsed lyrics:\n[#{lyrics_data.lyrics}]" ) if verbose_log?
372
+ response.lyrics = Strings.cleanup_lyrics( response.lyrics ) if response.lyrics && @cleanup_lyrics
373
+ end
374
+
375
+ return response
376
+
377
+ end
378
+
379
+ # Returns a FetchPageData instance
380
+ def build_suggestions_fetch_data( request )
381
+ return FetchPageData.new( nil )
382
+ end
383
+
384
+ # Returns the lyrics page body (nil if it couldn't be retrieved) and the response url
385
+ def fetch_suggestions_page( fetch_data )
386
+
387
+ return nil, nil if ! fetch_data.url
388
+
389
+ log( fetch_data.to_s() + "\nFetching suggestions page... ", 0 )
390
+
391
+ if fetch_data.post_data
392
+ response, url = HTTP.fetch_page_post( fetch_data.url, fetch_data.post_data, fetch_data.headers_data )
393
+ else
394
+ response, url = HTTP.fetch_page_get( fetch_data.url, fetch_data.headers_data )
395
+ end
396
+
397
+ if response && response.body()
398
+ log( "OK" )
399
+ log( response.body(), 2 ) if verbose_log?
400
+ return response.body(), url
401
+ else
402
+ log( "ERROR" )
403
+ return nil, url
404
+ end
405
+
406
+ end
407
+
408
+ # NOTE: override for example if your using google feeling lucky to verify that
409
+ # the resulting page is not just some random (trashy) result.
410
+ # Returns false is body does not correspond to the required suggestions page
411
+ def suggestions_page_valid?( request, page_body, page_url )
412
+ return true
413
+ end
414
+
415
+ # NOTE: implement in subclasses
416
+ # Returns an array of Suggestion objects
417
+ # def parse_suggestions( request, page_body, page_url )
418
+ # return []
419
+ # end
420
+
421
+ # Returns an array of maps { url, artist, title }
422
+ def suggestions( request )
423
+
424
+ log( site_name().upcase() + "\nRetrieving SUGGESTIONS...\n" + request.to_s() )
425
+
426
+ fetch_data = build_suggestions_fetch_data( request )
427
+ suggestions = []
428
+
429
+ suggestions_rec( request, fetch_data, suggestions, 0 )
430
+
431
+ return suggestions
432
+
433
+ end
434
+
435
+ def suggestions_rec( request, fetch_data, cumulative_suggestions, recursion_level )
436
+
437
+ page_body, page_url = fetch_suggestions_page( fetch_data )
438
+ return if page_body == nil
439
+
440
+ if suggestions_page_valid?( request, page_body, page_url )
441
+
442
+ log( "Parsing suggestions...", 0 )
443
+ suggestions = parse_suggestions( request, page_body, page_url )
444
+
445
+ if ! suggestions.is_a?( Array )
446
+ log( " INVALID RESPONSE FROM PARSE_SUGGESTIONS" )
447
+ return
448
+ end
449
+
450
+ if suggestions.empty?
451
+ log( " NO SUGGESTIONS FOUND" )
452
+ return
453
+ else
454
+ log( "" )
455
+ end
456
+
457
+ suggestions.each() do |suggestion|
458
+ if suggestion.is_a?( FetchPageData )
459
+ log( " - url: #{suggestion.url}" )
460
+ increase_tabulation_level()
461
+ log( "Retrieving SUGGESTIONS LEVEL#{recursion_level+2}..." )
462
+ suggestions_rec( request, suggestion, cumulative_suggestions, recursion_level + 1 )
463
+ decrease_tabulation_level()
464
+ else
465
+ log( " - #{suggestion.to_s()}" )
466
+ cumulative_suggestions << suggestion
467
+ end
468
+ end
469
+
470
+ else
471
+ log( "SUGGESTIONS PAGE IS INVALID" )
472
+ end
473
+
474
+ end
475
+ protected :suggestions_rec
476
+
477
+
478
+ def lyrics_from_suggestions( request, suggestions=nil )
479
+
480
+ log( site_name().upcase() + "\nSearching LYRICS FROM SUGGESTIONS..." )
481
+
482
+ suggestions = suggestions( request ) if suggestions == nil
483
+
484
+ normalized_artist, normalized_title = Strings.normalize( request.artist ), Strings.normalize( request.title )
485
+
486
+ log( "Scanning suggestions... ", 0 )
487
+ suggestions.each() do |suggestion|
488
+ next if ! suggestion.is_a?( Suggestion )
489
+ if Strings.normalize( suggestion.artist ) == normalized_artist &&
490
+ Strings.normalize( suggestion.title ) == normalized_title
491
+ log( "MATCH FOUND\n" + suggestion.to_s() )
492
+ # TODO should we use request.album and request.year here?
493
+ response = lyrics_from_url(
494
+ Request.new( suggestion.artist, suggestion.title, request.album, request.year ),
495
+ suggestion.url
496
+ )
497
+ return response, suggestions if response.lyrics
498
+ end
499
+ end
500
+
501
+ log( "NO VALID MATCH FOUND" )
502
+
503
+ return Response.new( request ), suggestions
504
+
505
+ end
506
+
507
+ # Returns a Lyrics::Response object
508
+ def lyrics_full_search( request )
509
+
510
+ # LYRICS DIRECT SEARCH:
511
+ response = lyrics_direct_search( request )
512
+ return response if response.lyrics
513
+
514
+ # NOT FOUND, SEARCH IN SUGGESTIONS:
515
+ response, suggs = lyrics_from_suggestions( request, suggestions( request ) )
516
+ return response
517
+ end
518
+
519
+ # default implementation, overide if there is a more accurate expression for a specific site
520
+ def Lyrics.build_google_feeling_lucky_url( artist, title=nil )
521
+ query = Strings.google_search_quote( artist )
522
+ query << " " << Strings.google_search_quote( title ) if title
523
+ query << " lyrics"
524
+ return Strings.build_google_feeling_lucky_url( query, self.site_host() )
525
+ end
526
+
527
+ def build_google_feeling_lucky_url( artist, title=nil )
528
+ return self.class.build_google_feeling_lucky_url( artist, title )
529
+ end
530
+
531
+ # NOTE: if possible use popular song that are likely to be indexed by google.
532
+ # We are not testing here how good or complete a site is but if the site plugin
533
+ # is behaving as expected. We part from the assumption that every plugin should
534
+ # be able to get lyrics and suggestions for the values returned here.
535
+ def Lyrics.lyrics_test_data()
536
+ return [
537
+ Request.new( "Nirvana", "Smells Like Teen Spirit", "Nevermind" ),
538
+ Request.new( "Radiohead", "Optimistic", "Kid A" ),
539
+ Request.new( "Massive Attack", "Protection", "Protection" ),
540
+ Request.new( "Pearl Jam", "Porch", "Ten" ),
541
+ # Request.new( "The Cranberries", "Linger", "Everybody Else Is Doing It, So Why Can't We?" ),
542
+ # Request.new( "Radiohead", "Idioteque", "Kid A" ),
543
+ # Request.new( "Radiohead", "How To Disappear Completely","stitle"=>"How To Dissapear", "Kid A" ),
544
+ # Request.new( "Pearl Jam", "Love Boat Captain", "Riot Act" ),
545
+ # Request.new( "Blur", "No Distance Left To Run", "13" ),
546
+ # Request.new( "Opeth", "Hope Leaves", "Damnation" ),
547
+ # Request.new( "Megadeth", "À tout le monde", "Youthanasia" ),
548
+ # Request.new( "Metallica", "Master of Puppets", "Master of Puppets" ),
549
+ # Request.new( "Lacuna Coil", "Heaven's a Lie", "Comalies" ),
550
+ # Request.new( "Pearl Jam", "Porch", "Ten" ),
551
+ # Request.new( "Portishead", "Wandering Star", "Dummy" ),
552
+ ]
553
+ end
554
+
555
+ def lyrics_test_data()
556
+ return self.class.lyrics_test_data()
557
+ end
558
+
559
+ def Lyrics.suggestions_test_data()
560
+ return lyrics_test_data()
561
+ end
562
+
563
+ def suggestions_test_data()
564
+ return self.class.suggestions_test_data()
565
+ end
566
+
567
+ end