karaoke 0.0.1

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.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "activesupport"
6
+ gem "terminal-notifier"
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 Douwe Maan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # karaoke
2
+
3
+ A Ruby library for easily finding the lyrics to your favorite songs.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ gem install karaoke
9
+ ```
10
+
11
+ Or in your Gemfile:
12
+
13
+ ```ruby
14
+ gem "karaoke"
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```ruby
20
+ require "karaoke"
21
+
22
+ # To find the lyrics to a song, simply search for it:
23
+ song = Karaoke.search("Armin van Buuren - This Is What It Feels Like")
24
+ # Be as creative as you want, karaoke usually knows what you're looking for:
25
+ song = Karaoke.search("This Is What It Feels Like - Armin van Buuren")
26
+ song = Karaoke.search("This Is What It Feels Like by Armin van Buuren")
27
+ song = Karaoke.search("Song by Armin van Buuren with vocals by Trevor Guthrie")
28
+
29
+ # This will return a Song object containing the following information:
30
+ song.artist # => "Armin van Buuren"
31
+ song.title # => "This Is What It Feels Like"
32
+ song.lyrics # => "Nobody here knocking at my door\n" and so on.
33
+
34
+ # If you're only interested in the lyrics, save a couple of keystrokes:
35
+ lyrics = Karaoke.lyrics("Armin van Buuren - This Is What It Feels Like")
36
+ # => "Nobody here knocking at my door\n" and so on.
37
+
38
+ # Behind the scenes, karaoke searches Google to find results on any of these sites:
39
+ # (Rap)Genius, MetroLyrics, Lyricsfreak, AZLyrics, DirectLyrics and LyricsMania
40
+ # Next, karaoke reads the lyrics from the highest ranked Google search result.
41
+
42
+ # If, for whatever reason, you want access to all of the found lyrics
43
+ # instead of just the highest ranked one, use `Karaoke::Search` directly:
44
+ Karaoke::Search.new("Armin van Buuren - This Is What It Feels Like").results
45
+ # => [
46
+ # #<Karaoke::Song::Azlyrics:0x007fef3a080b98>,
47
+ # #<Karaoke::Song::MetroLyrics:0x007fef3c01c9c0>,
48
+ # #<Karaoke::Song::Genius:0x007fef3a0cf900>,
49
+ # #<Karaoke::Song::LyricsMania:0x007fef3a0d1a98>
50
+ # ]
51
+ ```
52
+
53
+ ## Examples
54
+ Check out the [`examples/`](examples) folder for an example that I actually use myself.
55
+
56
+ ## License
57
+ Copyright (c) 2014 Douwe Maan
58
+
59
+ Permission is hereby granted, free of charge, to any person obtaining
60
+ a copy of this software and associated documentation files (the
61
+ "Software"), to deal in the Software without restriction, including
62
+ without limitation the rights to use, copy, modify, merge, publish,
63
+ distribute, sublicense, and/or sell copies of the Software, and to
64
+ permit persons to whom the Software is furnished to do so, subject to
65
+ the following conditions:
66
+
67
+ The above copyright notice and this permission notice shall be
68
+ included in all copies or substantial portions of the Software.
69
+
70
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
71
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
72
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
73
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
74
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
75
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
76
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ spec = Gem::Specification.load("karaoke.gemspec")
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
8
+
9
+ desc "Build the .gem file"
10
+ task :build do
11
+ system "gem build #{spec.name}.gemspec"
12
+ end
13
+
14
+ desc "Push the .gem file to rubygems.org"
15
+ task release: :build do
16
+ system "gem push #{spec.name}-#{spec.version}.gem"
17
+ end
data/lib/karaoke.rb ADDED
@@ -0,0 +1,16 @@
1
+ require "karaoke/version"
2
+ require "karaoke/search"
3
+
4
+ module Karaoke
5
+ class << self
6
+ def search(query)
7
+ Search.new(query).results.first
8
+ end
9
+
10
+ def lyrics(query)
11
+ song = search(query)
12
+
13
+ song && song.lyrics
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ require "google-search"
2
+
3
+ require "karaoke/song"
4
+
5
+ module Karaoke
6
+ class Search
7
+ attr_reader :query
8
+
9
+ def initialize(query)
10
+ @query = query
11
+ end
12
+
13
+ def search_query
14
+ "#{self.query} lyrics"
15
+ end
16
+
17
+ def results
18
+ @results ||= begin
19
+ song_results = []
20
+
21
+ search_results = Google::Search::Web.new(query: search_query)
22
+ search_results.each do |result|
23
+ klass = Song.song_type_for(result.uri)
24
+ next unless klass
25
+
26
+ song_results << klass.new(result.uri)
27
+ end
28
+
29
+ song_results
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ module Karaoke
2
+ module Song
3
+ class << self
4
+ @@services = []
5
+
6
+ def register_song_type(song_type)
7
+ @@services |= [song_type]
8
+ end
9
+
10
+ def song_type_for(lyrics_uri)
11
+ @@services.find { |song_type| song_type.matches?(lyrics_uri) }
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ require "karaoke/song/genius"
18
+ require "karaoke/song/metro_lyrics"
19
+ require "karaoke/song/lyricsfreak"
20
+ # require "karaoke/song/lyricsmode"
21
+ require "karaoke/song/azlyrics"
22
+ require "karaoke/song/directlyrics"
23
+ require "karaoke/song/lyrics_mania"
@@ -0,0 +1,39 @@
1
+ require "karaoke/song/base"
2
+
3
+ module Karaoke
4
+ module Song
5
+ class Azlyrics < Base
6
+ def self.matches?(lyrics_url)
7
+ URI.parse(lyrics_url).host.end_with?("azlyrics.com")
8
+ end
9
+
10
+ def artist
11
+ return @artist if defined?(@artist)
12
+
13
+ @artist = data.match(/ArtistName = "([^"]+)";/)[1]
14
+ end
15
+
16
+ def title
17
+ return @title if defined?(@title)
18
+
19
+ @title = data.match(/SongName = "([^"]+)";/)[1]
20
+ end
21
+
22
+ def lyrics
23
+ return @lyrics if defined?(@lyrics)
24
+
25
+ el = document.css("div[style='margin-left:10px;margin-right:10px;']").first
26
+ return @lyrics = nil unless el
27
+
28
+ el.css("i").each { |i_el| i_el.replace(i_el.inner_html) }
29
+
30
+ lyrics = clean_html(el.inner_html)
31
+ lyrics.gsub!("<!-- start of lyrics -->", "")
32
+ lyrics.gsub!("<!-- end of lyrics -->", "")
33
+ @lyrics = lyrics
34
+ end
35
+ end
36
+
37
+ register_song_type Azlyrics
38
+ end
39
+ end
@@ -0,0 +1,56 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "nokogiri"
4
+
5
+ module Karaoke
6
+ module Song
7
+ class Base
8
+ attr_reader :lyrics_url
9
+
10
+ def initialize(lyrics_url)
11
+ @lyrics_url = lyrics_url
12
+ end
13
+
14
+ def artist
15
+ raise NotImplementedError.new("The Song::Base subclass needs to override the #artist method.")
16
+ end
17
+
18
+ def title
19
+ raise NotImplementedError.new("The Song::Base subclass needs to override the #title method.")
20
+ end
21
+
22
+ def lyrics
23
+ raise NotImplementedError.new("The Song::Base subclass needs to override the #lyrics method.")
24
+ end
25
+
26
+ private
27
+ def data
28
+ @data ||= begin
29
+ redirects = 0
30
+ begin
31
+ raise "Too many redirects" if redirects == 5
32
+
33
+ uri = URI.parse(@lyrics_url)
34
+ connection = Net::HTTP.new(uri.host, uri.port)
35
+ connection.use_ssl = uri.is_a?(URI::HTTPS)
36
+
37
+ request = Net::HTTP::Get.new(uri.request_uri)
38
+ response = connection.request(request)
39
+
40
+ redirects += 1
41
+ end while response.is_a?(Net::HTTPRedirection) && @lyrics_url = response["location"]
42
+
43
+ response.body
44
+ end
45
+ end
46
+
47
+ def document
48
+ @document ||= Nokogiri::HTML::Document.parse(data)
49
+ end
50
+
51
+ def clean_html(html)
52
+ html.gsub(/[\t\n\r]/, "").gsub("<br>", "\n")
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,48 @@
1
+ require "karaoke/song/base"
2
+
3
+ module Karaoke
4
+ module Song
5
+ class Directlyrics < Base
6
+ def self.matches?(lyrics_url)
7
+ URI.parse(lyrics_url).host.end_with?("directlyrics.com")
8
+ end
9
+
10
+ def artist
11
+ return @artist if defined?(@artist)
12
+
13
+ el = document.css(".breadcrumb a[href$='-artist.html']").first ||
14
+ document.css("#hd-bar a[href$='-artist.html']").first
15
+
16
+ return @artist = nil unless el
17
+
18
+ @artist = el.text
19
+ end
20
+
21
+ def title
22
+ return @title if defined?(@title)
23
+
24
+ el = document.css(".hd h1").first || document.css("#hd h1").first
25
+ return @title = nil unless el
26
+
27
+ @title = el.text.match("^[^-]+- (.+?) lyrics$")[1]
28
+ end
29
+
30
+ def lyrics
31
+ return @lyrics if defined?(@lyrics)
32
+
33
+ el = document.css(".lyrics p").first || begin
34
+ lyrics_class = data.match(/\$\('\.([a-z0-9]+)'\)\.addClass\('lyrics'\);/)[1]
35
+ document.css("[class='#{lyrics_class}'] p").first
36
+ end
37
+
38
+ return @lyrics = nil unless el
39
+
40
+ el.css("ins").remove
41
+
42
+ @lyrics = clean_html(el.inner_html)
43
+ end
44
+ end
45
+
46
+ register_song_type Directlyrics
47
+ end
48
+ end
@@ -0,0 +1,37 @@
1
+ require "karaoke/song/base"
2
+
3
+ module Karaoke
4
+ module Song
5
+ class Genius < Base
6
+ def self.matches?(lyrics_url)
7
+ URI.parse(lyrics_url).host.end_with?("genius.com") # Matches new *.genius.com URLs as well as rapgenius.com.
8
+ end
9
+
10
+ def artist
11
+ @artist ||= tracking_data["Primary Artist"] || tracking_data["Primary Arist"]
12
+ end
13
+
14
+ def title
15
+ @title ||= tracking_data["Title"]
16
+ end
17
+
18
+ def lyrics
19
+ return @lyrics if defined?(@lyrics)
20
+
21
+ el = document.css(".lyrics p").first
22
+ return @lyrics = nil unless el
23
+
24
+ el.css("a").each { |a_el| a_el.replace(a_el.inner_html) }
25
+
26
+ @lyrics = clean_html(el.inner_html)
27
+ end
28
+
29
+ private
30
+ def tracking_data
31
+ @tracking_data ||= JSON.parse(data.match("var TRACKING_DATA = (\{.+?\})")[1])
32
+ end
33
+ end
34
+
35
+ register_song_type Genius
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require "karaoke/song/base"
2
+
3
+ module Karaoke
4
+ module Song
5
+ class LyricsMania < Base
6
+ def self.matches?(lyrics_url)
7
+ URI.parse(lyrics_url).host.end_with?("lyricsmania.com")
8
+ end
9
+
10
+ def artist
11
+ return @artist if defined?(@artist)
12
+
13
+ @artist = data.match(/cf_page_artist = "([^"]+)";/)[1]
14
+ end
15
+
16
+ def title
17
+ return @title if defined?(@title)
18
+
19
+ @title = data.match(/cf_page_song = "([^"]+)";/)[1]
20
+ end
21
+
22
+ def lyrics
23
+ return @lyrics if defined?(@lyrics)
24
+
25
+ el = document.css(".lyrics-body").first
26
+ return @lyrics = nil unless el
27
+
28
+ el.css("div").remove
29
+ el.css("strong").remove
30
+
31
+ @lyrics = clean_html(el.inner_html)
32
+ end
33
+ end
34
+
35
+ register_song_type LyricsMania
36
+ end
37
+ end
@@ -0,0 +1,40 @@
1
+ require "karaoke/song/base"
2
+
3
+ module Karaoke
4
+ module Song
5
+ class Lyricsfreak < Base
6
+ def self.matches?(lyrics_url)
7
+ URI.parse(lyrics_url).host.end_with?("lyricsfreak.com")
8
+ end
9
+
10
+ def artist
11
+ return @artist if defined?(@artist)
12
+
13
+ el = document.css(%Q{a[data-tracking='\["Meta","Lyrics","ArtistName"\]']}).first
14
+ return @artist = nil unless el
15
+
16
+ @artist = el.text
17
+ end
18
+
19
+ def title
20
+ return @title if defined?(@title)
21
+
22
+ el = document.css(".song-info-panel h1").first
23
+ return @title = nil unless el
24
+
25
+ @title = el.text.match("^(.+?) Lyrics$")[1]
26
+ end
27
+
28
+ def lyrics
29
+ return @lyrics if defined?(@lyrics)
30
+
31
+ el = document.css("#content_h").first
32
+ return @lyrics = nil unless el
33
+
34
+ @lyrics = clean_html(el.inner_html)
35
+ end
36
+ end
37
+
38
+ register_song_type Lyricsfreak
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+ require "karaoke/song/base"
2
+
3
+ # Doesn't work, because Lyricsmode blocks access from bots with 403 Forbidden.
4
+
5
+ module Karaoke
6
+ module Song
7
+ class Lyricsmode < Base
8
+ def self.matches?(lyrics_url)
9
+ URI.parse(lyrics_url).host.end_with?("lyricsmode.com")
10
+ end
11
+
12
+ def artist
13
+ return @artist if defined?(@artist)
14
+
15
+ el = document.css(%Q{a[data-tracking='\["Meta","Lyrics","ArtistName"\]']}).first
16
+ return @artist = nil unless el
17
+
18
+ @artist = el.text
19
+ end
20
+
21
+ def title
22
+ return @title if defined?(@title)
23
+
24
+ el = document.css("h1.song_name").first
25
+ return @title = nil unless el
26
+
27
+ @title = el.text.trim.match("^(.+?) Lyrics$")[1]
28
+ end
29
+
30
+ def lyrics
31
+ return @lyrics if defined?(@lyrics)
32
+
33
+ el = document.css(".meaning_inside_frame").first
34
+ return @lyrics = nil unless el
35
+
36
+ @lyrics = clean_html(el.inner_html)
37
+ end
38
+ end
39
+
40
+ register_song_type Lyricsmode
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ require "karaoke/song/base"
2
+
3
+ module Karaoke
4
+ module Song
5
+ class MetroLyrics < Base
6
+ def self.matches?(lyrics_url)
7
+ uri = URI.parse(lyrics_url)
8
+
9
+ uri.host.end_with?("metrolyrics.com") && !uri.path.end_with?("-lyrics.html")
10
+ end
11
+
12
+ def artist
13
+ @artist ||= tracking_data["musicArtistName"]
14
+ end
15
+
16
+ def title
17
+ @title ||= tracking_data["musicSongTitle"]
18
+ end
19
+
20
+ def lyrics
21
+ return @lyrics if defined?(@lyrics)
22
+
23
+ el = document.css("#lyrics-body-text").first
24
+ return @lyrics = nil unless el
25
+
26
+ lyrics = clean_html(el.inner_html)
27
+ lyrics.gsub!("</p><p class=\"verse\">", "\n\n")
28
+ lyrics.gsub!("<p class=\"verse\">", "")
29
+ lyrics.gsub!("</p>", "")
30
+ @lyrics = lyrics
31
+ end
32
+
33
+ private
34
+ def tracking_data
35
+ @tracking_data ||= JSON.parse(data.match("var utag_data=(\{.+?\})")[1])
36
+ end
37
+ end
38
+
39
+ register_song_type MetroLyrics
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Karaoke
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+
4
+ require "rspec"
5
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
6
+
7
+ require "karaoke"
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: karaoke
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Douwe Maan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-09-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: &70358367183500 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70358367183500
25
+ - !ruby/object:Gem::Dependency
26
+ name: google-search
27
+ requirement: &70358367183080 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70358367183080
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &70358367182660 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70358367182660
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: &70358367182060 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70358367182060
58
+ description: A Ruby library for easily finding the lyrics to your favorite songs
59
+ email: douwe@selenight.nl
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - lib/karaoke/search.rb
65
+ - lib/karaoke/song/azlyrics.rb
66
+ - lib/karaoke/song/base.rb
67
+ - lib/karaoke/song/directlyrics.rb
68
+ - lib/karaoke/song/genius.rb
69
+ - lib/karaoke/song/lyrics_mania.rb
70
+ - lib/karaoke/song/lyricsfreak.rb
71
+ - lib/karaoke/song/lyricsmode.rb
72
+ - lib/karaoke/song/metro_lyrics.rb
73
+ - lib/karaoke/song.rb
74
+ - lib/karaoke/version.rb
75
+ - lib/karaoke.rb
76
+ - LICENSE
77
+ - README.md
78
+ - Rakefile
79
+ - Gemfile
80
+ - spec/spec_helper.rb
81
+ homepage: https://github.com/DouweM/karaoke
82
+ licenses:
83
+ - MIT
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ segments:
95
+ - 0
96
+ hash: 1283531912695341570
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ segments:
104
+ - 0
105
+ hash: 1283531912695341570
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 1.8.6
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Find lyrics to your favorite songs
112
+ test_files:
113
+ - spec/spec_helper.rb