mp3lyrics 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 653d2e2af1835374a3c2d0487ab791b307dcb8c2cc0d5cacd5206ff1e4cacd9c
4
+ data.tar.gz: 76091b679f8465982f3bb83df1a418d94967161f93b5fcf6603db4fdc9601b72
5
+ SHA512:
6
+ metadata.gz: 610dc8c9d0a03e04c4357bf1dd541f2eddd2a684411bc2f7f07af21ec5d25d309ed673be0d650c769a685e5e306a0e3773a11d447ad23ec641f3e73ae7d46497
7
+ data.tar.gz: 9367bb514a4cc5997619345c3527b7d6dcbfe56484d53a0a20b9a489d84b4ef571249a949656fdab1f2f825612af34e06ee8ff69c4362cef70fa80e828788fe9
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Tim Brust
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,86 @@
1
+ # MP3Lyrics
2
+ [![Ruby](https://github.com/timbru31/mp3lyrics/workflows/Ruby/badge.svg)](https://github.com/timbru31/mp3lyrics/actions?query=workflow%3ARuby)
3
+
4
+ [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/timbru31/mp3lyrics.svg)](https://codeclimate.com/github/timbru31/mp3lyrics/coverage)
5
+ ![Code Climate](https://img.shields.io/codeclimate/maintainability/timbru31/mp3lyrics.svg)
6
+ [![Known Vulnerabilities](https://snyk.io/test/github/timbru31/mp3lyrics/badge.svg)](https://snyk.io/test/github/timbru31/mp3lyrics)
7
+ [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=timbru31/mp3lyrics)](https://dependabot.com)
8
+
9
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)
10
+
11
+ ### Still in Alpha, ugly and WIP :)
12
+
13
+ ## Info
14
+
15
+ MP3Lyrics is a tool written in Ruby (currently only CLI) to download song lyrics from [LyricWiki](https://lyrics.fandom.com/wiki/LyricWikim), [MetroLyrics](http://metrolyrics.com), [AZLyrics](https://www.azlyrics.com) and [Genius](https://genius.com).
16
+ The lyrics are added to the mp3 file via the [USLT (Unsynchronised lyric/text transcription)](http://id3.org/id3v2.4.0-frames) tag (with the power of [ruby-mp3info](https://github.com/moumar/ruby-mp3info)).
17
+
18
+ ## Motivation
19
+
20
+ #### Why Ruby?
21
+
22
+ Because I wanted to try another language than Java.
23
+
24
+ #### Why are you not using the LyricWiki API?
25
+
26
+ ~~Although they have got a pretty neat [REST API](http://api.wikia.com/wiki/LyricWiki_API/REST) the lyrics can't be fully retrieved, most likely due to licensing issues.~~
27
+ They no longer offer an API since January 2016.
28
+
29
+ #### Why are you not using musiXmatch API?
30
+
31
+ Simply because they charge me (the developer) for retrieving the lyrics.
32
+
33
+ #### Why are you not using MetroLyrics API?
34
+
35
+ I don't have an API key (yet).
36
+
37
+ #### Why are you not using Genius API?
38
+
39
+ They do not offer fetching lyrics via the API (yet).
40
+
41
+ #### Why not iTunes Lyrics Adder, Lyrics for iTunes, iSongText, Get Lyrical, ...?
42
+
43
+ Either they were tied to iTunes and ugly DLL libraries, outdated/inactive, closed source or not cross platform. I'm mainly developing on my MacBook and my iTunes library is on my Windows system. I need something that works anywhere.
44
+
45
+ By dropping the need (and support) of iTunes running somewhere in the background, the tool is more lightweight.
46
+
47
+ ## Dependencies
48
+
49
+ 1. Bundler
50
+ 2. Nokogiri for HTML parsing
51
+ 3. mp3info as a MP3 library
52
+ 4. require_all for easier requiring of the wiki_api folder
53
+ 5. For testing purposes you should install rubocop, rake, minitest and optionally coverage reporters for CodeClimate
54
+
55
+ ## Installation
56
+
57
+
58
+ ```shell
59
+ gem install bundler
60
+ bundle install
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ ```shell
66
+ ./mp3lyrics.rb <dir> [-override true/false] [-use lyricwiki/genius/metrolyrics/azlyrics]
67
+ ```
68
+ **dir** is the folder with your music, it's iterated recursively.
69
+ **override** is a boolean toggle to override existing lyrics (defaults to *false*).
70
+ **use** is a specific wiki to be used to download lyrics (all wikis are used by default).
71
+
72
+ ## Tests
73
+
74
+ To execute the tests run
75
+ ```shell
76
+ bundle exec rake
77
+ ```
78
+
79
+ ## Future plans // ToDo
80
+
81
+ - ~~Add support to other lyrics sites when 404 is returned from LyricWiki~~
82
+ - GUI for easier usage (maybe)
83
+ - Add test cases, see [#1](https://github.com/timbru31/mp3lyrics/issues/1)
84
+
85
+ ---
86
+ Built by (c) Tim Brust and contributors. Released under the MIT license.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'mp3lyrics'
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'mp3info'
5
+ require 'require_all'
6
+
7
+ require_all './lib/wiki/'
8
+
9
+ def to_b(string)
10
+ !(string =~ /^(true|t|yes|y|1)$/i).nil?
11
+ end
12
+
13
+ def usage_message(override_options, use_options)
14
+ 'Usage: ./mp3lyrics.rb <dir> [-override ' + override_options.join('/') + '] [-use ' + use_options.join('/') + ']'
15
+ end
16
+
17
+ override_options = [true, false]
18
+ use_options = %w[lyricwiki genius metrolyrics azlyrics]
19
+
20
+ if ARGV.length.even? ||
21
+ ARGV.count('-override') > 1 ||
22
+ ARGV.count('-use') > 1
23
+ # If there is an even number of arguments (includes no arguments)
24
+ # or a flag has been used more than once
25
+ puts usage_message(override_options, use_options)
26
+ exit
27
+ end
28
+
29
+ dir = ARGV[0]
30
+ override = false
31
+ wiki_to_use = nil
32
+
33
+ i = 1
34
+ while i < ARGV.length
35
+ if ARGV[i] == '-override'
36
+ if override_options.include?(to_b(ARGV[i + 1]))
37
+ override = to_b(ARGV[i + 1])
38
+ else
39
+ # If the argument after the override flag is invalid
40
+ puts usage_message(override_options, use_options)
41
+ exit
42
+ end
43
+ elsif ARGV[i] == '-use'
44
+ if use_options.include?(ARGV[i + 1])
45
+ wiki_to_use = ARGV[i + 1]
46
+ else
47
+ # If the argument after the use flag is invalid
48
+ puts usage_message(override_options, use_options)
49
+ exit
50
+ end
51
+ else
52
+ # If the argument is not a valid flag
53
+ puts usage_message(override_options, use_options)
54
+ exit
55
+ end
56
+ i += 2
57
+ end
58
+
59
+ puts "
60
+ Welcome to
61
+ __ __ _____ ____ _ _
62
+ | \\\/ | __ \\___ \\| | (_)
63
+ | \\ \/ | |__) |__) | | _ _ _ __ _ ___ ___
64
+ | |\\/| | ___\/|__ <| | | | | | \'__| |\/ __\/ __|
65
+ | | | | | ___) | |___| |_| | | | | (__\\__ \\
66
+ |_| |_|_| |____\/|______\\__, |_| |_|\\___|___\/
67
+ __\/ |
68
+ |___\/
69
+
70
+ The current working directory is #{dir}
71
+ Overriding existing lyrics is #{override}"
72
+ puts "Specific wiki to use is #{wiki_to_use}" unless wiki_to_use.nil?
73
+ puts "\n\r"
74
+
75
+ wiki = Wiki.new
76
+ files = Dir.glob("#{dir}/**/*")
77
+ files.each do |file|
78
+ filename = File.extname(file)
79
+ next unless filename == '.mp3'
80
+
81
+ Mp3Info.open(file) do |mp3|
82
+ artist = mp3.tag.artist || mp3.tag1.artist || mp3.tag2.artist
83
+ title = mp3.tag.title || mp3.tag1.title || mp3.tag2.title
84
+ if artist.nil? || title.nil?
85
+ puts "Skipping song because title or artist is not set...\n\r"
86
+ next
87
+ end
88
+ puts "Fetching lyrics for #{artist} - #{title}"
89
+ # Either no tag is set, the mp3 file has no USLT tag or we override anyway
90
+ if !mp3.hastag2? || (mp3.hastag2? && !mp3.tag2.key?('USLT')) || override
91
+ lyrics = nil
92
+ lyrics = LyricWiki.new.get_lyrics(artist, title) if wiki_to_use.nil? || wiki_to_use == 'lyricwiki'
93
+ if wiki_to_use.nil? || wiki_to_use == 'genius'
94
+ lyrics = Genius.new.get_lyrics(artist, title) if lyrics.nil?
95
+ end
96
+ if wiki_to_use.nil? || wiki_to_use == 'metrolyrics'
97
+ lyrics = MetroLyrics.new.get_lyrics(artist, title) if lyrics.nil?
98
+ end
99
+ if wiki_to_use.nil? || wiki_to_use == 'azlyrics'
100
+ lyrics = AZLyrics.new.get_lyrics(artist, title) if lyrics.nil?
101
+ end
102
+ if lyrics.nil?
103
+ puts "Did not find any lyrics\n\r"
104
+ else
105
+ puts "Lyrics found\n\r"
106
+ wiki.set_lyrics(mp3, lyrics)
107
+ end
108
+ else
109
+ puts "Skipping #{artist} - #{title} because lyrics are already set"
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './wiki'
4
+
5
+ # Fetches the lyrics from AZLyrics.com
6
+ # Lyrics are stored accessed via the URL schema https://www.azlyrics.com/lyrics/ARTIST/SONG.html
7
+ # They are inside /html/body/div[2]/div/div[2]/div[5], sadly no class to access.
8
+ # There are hidden comments, which need to be removed.
9
+ class AZLyrics < Wiki
10
+ def get_lyrics(artist, song, limit = 10)
11
+ artist = artist.delete(' ').parameterize
12
+ song = song.delete(' ').parameterize
13
+
14
+ res = fetch("https://www.azlyrics.com/lyrics/#{artist.downcase}/#{song.downcase}.html", limit)
15
+ return nil unless res.is_a? Net::HTTPSuccess
16
+
17
+ lyrics = Nokogiri::HTML(res.body).xpath('/html/body/div[2]/div/div[2]/div[5]')
18
+ prettify_lyrics(lyrics)
19
+ end
20
+
21
+ def prettify_lyrics(lyrics)
22
+ lyrics.search('.//comment()').remove
23
+ lyrics.inner_html.gsub('<br>', "\r").delete("\n").sub("\r\r", '')
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './wiki'
4
+
5
+ # Fetches the lyrics from Genius (formerly known as Rap Genius)
6
+ # Lyrics are stored accessed via the URL schema https://genius.com/ARTIST-SONG-lyrics
7
+ # They are inside //div[@class="lyrics"]//p[1], a lyrics element with the class "lyrics"
8
+ # It seems that this element was added by Angular.js
9
+ # Inside the p in the lyrics div they are in a tags
10
+ class Genius < Wiki
11
+ def get_lyrics(artist, song, limit = 10)
12
+ artist = artist.tr(' ', '-').parameterize
13
+ song = song.tr(' ', '-').parameterize
14
+
15
+ res = fetch("https://genius.com/#{artist.downcase}-#{song.downcase}-lyrics", limit)
16
+ return nil unless res.is_a? Net::HTTPSuccess
17
+
18
+ lyrics = Nokogiri::HTML(res.body).xpath('//div[@class="lyrics"]//p[1]')
19
+ lyrics.inner_html.gsub('<br>', "\r").gsub(%r{</?[^>]+>}, '').delete("\n")
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './wiki'
4
+
5
+ # Fetches the lyrics from LyricWiki
6
+ # Lyrics are stored accessed via the URL schema https://lyrics.fandom.com/wiki/ARTIST:SONG
7
+ # They are inside //div[@class="lyricbox"], a div with the class "lyricbox"
8
+ # There are hidden comments and script tags, that needs to be removed.
9
+ class LyricWiki < Wiki
10
+ def get_lyrics(artist, song, limit = 10)
11
+ artist = artist.tr(' ', '_')
12
+ song = song.tr(' ', '_')
13
+
14
+ res = fetch("https://lyrics.fandom.com/wiki/#{artist}:#{song}", limit)
15
+ return nil unless res.is_a? Net::HTTPSuccess
16
+
17
+ lyrics = Nokogiri::HTML(res.body).xpath('//div[@class="lyricbox"]')
18
+ prettify_lyrics(lyrics)
19
+ end
20
+
21
+ def prettify_lyrics(lyrics)
22
+ lyrics.search('//script').remove
23
+ lyrics.search('.//comment()').remove
24
+ lyrics.inner_html.gsub('<br>', "\r").gsub(%r{</?[^>]+>}, '').delete("\n")
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './wiki'
4
+
5
+ # Fetches the lyrics from MetroLyrics
6
+ # Lyrics are stored accessed via the URL schema https://www.metrolyrics.com/SONG-lyrics-ARTIST
7
+ # They are inside //*[@id="lyrics-body-text"]
8
+ # There are hidden comments and script tags, that needs to be removed.
9
+ class MetroLyrics < Wiki
10
+ def get_lyrics(artist, song, limit = 10)
11
+ artist = artist.tr(' ', '-').parameterize
12
+ song = song.tr(' ', '-').parameterize
13
+
14
+ res = fetch("https://www.metrolyrics.com/#{song.downcase}-lyrics-#{artist.downcase}.html", limit)
15
+ return nil unless res.is_a? Net::HTTPSuccess
16
+
17
+ lyrics = Nokogiri::HTML(res.body).xpath('//*[@id="lyrics-body-text"]')
18
+ lyrics.search('.driver-photos').remove
19
+ lyrics.search('.driver-related').remove
20
+ prettify_lyrics(lyrics.inner_html)
21
+ end
22
+
23
+ def prettify_lyrics(lyrics)
24
+ lyrics = lyrics.gsub('<br>', "\r")
25
+ lyrics = lyrics.gsub("</p>\n<p class=\"verse\">", "\r")
26
+ lyrics = lyrics.gsub(%r{</?[^>]+>}, '')
27
+ lyrics = lyrics.delete("\t")
28
+ lyrics.delete("\n")
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/inflector'
4
+ require 'net/http'
5
+ require 'nokogiri'
6
+
7
+ # Base class to fetch different lyrics sites
8
+ class Wiki
9
+ def fetch(uri_str, limit = 10)
10
+ raise ArgumentError, 'The wiki site is redirecting too much, aborting...' if limit.zero?
11
+
12
+ uri = prepare_url(uri_str)
13
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
14
+ req = Net::HTTP::Get.new(uri.path)
15
+ begin
16
+ http.request(req)
17
+ rescue EOFError
18
+ fetch(uri_str, limit - 1)
19
+ end
20
+ end
21
+ case response
22
+ when Net::HTTPRedirection
23
+ fetch(response['location'], limit - 1)
24
+ else
25
+ response
26
+ end
27
+ end
28
+
29
+ def set_lyrics(mp3, lyrics)
30
+ mp3.tag2.USLT = lyrics
31
+ end
32
+
33
+ def prepare_url(uri_str)
34
+ uri_str.gsub!('%EF%BB%BF', '') # fix BOM
35
+ URI.parse(uri_str)
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mp3lyrics
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tim Brust
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2010-04-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: CLI for mp3 lyrics fetching. Uses ruby-mp3info and fetches different
14
+ lyric wikis
15
+ email: github@timbrust.de
16
+ executables:
17
+ - mp3lyrics
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - bin/mp3lyrics
24
+ - lib/mp3lyrics.rb
25
+ - lib/wiki/azlyrics.rb
26
+ - lib/wiki/genius.rb
27
+ - lib/wiki/lyricwiki.rb
28
+ - lib/wiki/metrolyrics.rb
29
+ - lib/wiki/wiki.rb
30
+ homepage: https://github.com/timbru31/
31
+ licenses:
32
+ - MIT
33
+ metadata: {}
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubygems_version: 3.0.6
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: CLI for mp3 lyrics fetching
53
+ test_files: []