lyrics 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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