music_metadata_score 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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ODhjMzM4ODU3YTFjOWU1NmExNjA4YWQ5YWFhNzU2MDllMjgwYzA1Nw==
5
+ data.tar.gz: !binary |-
6
+ NjcyYzU4YTA5NmI3Y2RmYmQwY2IzNjk2MzMwNWFkZGVkOTVmYzZmNw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ Y2MyMTAxMzAyMDY1YWJiNjdlNTQ3YWMzZGNiYTYzYWVhZmNkMGY5ZDNmNGUz
10
+ MzNhNTY5OWMwZDgwMTQzMjIyMjEwYTJmYzQ2YTRiODdkNjZmMDM5Mzc1ZWU3
11
+ MWVhMjc3NzRkMDk4NGZlYzgyNGFjM2FjOTA1OTc5YWIwMTgyZDM=
12
+ data.tar.gz: !binary |-
13
+ ZWMzZWRmNjg3YzA3ZjFiZDM0ZDRmMjNiYjY5MjRjNzRjY2RiMDVhMzE5ZjJi
14
+ NGQyYjA1ZjMwMmQyNDE4MGJkNTUyM2YwNWY1NGFlNjc4MTFlMzgyY2NkNzI3
15
+ OTUxMTczY2Y4NWY5YmI2NGVhODYzMDRlYTA4ODZmODE0ZmUwMWU=
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in music_metadata_score.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # MusicMetadataScore
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'music_metadata_score'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install music_metadata_score
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ #t.libs << "test"
6
+ #t.test_files = FileList['test/test*.rb']
7
+ #t.verbose = true
8
+ t.libs = ["lib"]
9
+ t.warning = true
10
+ t.verbose = true
11
+ t.test_files = FileList['test/test_*.rb']
12
+ end
13
+
14
+ desc "Run tests"
15
+ task :default => :test
@@ -0,0 +1,123 @@
1
+ class Artist
2
+
3
+ def self.are_proper_artist_format?(artists, override, level)
4
+ if artists && artists != []
5
+ Artist.loop_artists(artists, override, level)
6
+ else
7
+ false
8
+ end
9
+ end
10
+
11
+ def self.loop_artists(artists, override, level)
12
+ value = true
13
+ artists.each do |a|
14
+ if !Artist.is_proper_artist_format?(a, override, level)
15
+ value = false
16
+ break
17
+ end
18
+ end
19
+ value
20
+ end
21
+
22
+ def self.is_proper_artist_format?(artist, override, level)
23
+ if Artist.is_title_case?(artist, override) && !Artist.is_compound_artist?(artist, override) && !Artist.is_bad_various_artists?(artist) && Artist.artist_fits_level?(artist, override, level)
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ def self.is_title_case?(artist, override)
31
+ if MusicMetadataScore.is_title_case?(artist) || override == true
32
+ true
33
+ else
34
+ false
35
+ end
36
+ end
37
+
38
+ def self.is_compound_artist?(artist, override)
39
+ if override != true && (artist.downcase.include?('and') || artist.include?('&') || artist.downcase.include?('vs') || artist.downcase.include?('with') || artist.downcase.include?('feat') || artist.downcase.include?('present')) || artist.include?('/')
40
+ true
41
+ else
42
+ false
43
+ end
44
+ end
45
+
46
+ def self.is_bad_various_artists?(artist)
47
+ # various, va, v/a
48
+ if artist.downcase == 'various' || artist.downcase == 'va' || artist.downcase == 'v/a' || artist.downcase == 'various artist'
49
+ true
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ def self.artist_fits_level?(artist, override, level)
56
+ if override == false && level == :track && (artist.downcase == 'various artists' || artist.downcase == 'various artist' || artist.downcase == 'v/a' || artist.downcase == 'various')
57
+ false
58
+ else
59
+ true
60
+ end
61
+ end
62
+
63
+ def self.format_artists(artists, override, level)
64
+ array = []
65
+ artists.each do |a|
66
+ new_artist = Artist.title_case(a)
67
+ separated_artists = Artist.separate_compound_artists(new_artist)
68
+ separated_artists.each do |sa|
69
+ array << sa
70
+ end
71
+ end
72
+ return array
73
+ end
74
+
75
+ def self.title_case(artist)
76
+ new_artist = MusicMetadataScore.title_case(artist)
77
+ Artist.capitalize_the(new_artist)
78
+ end
79
+
80
+ def self.capitalize_the(string)
81
+ split = string.split(' ')
82
+ split.each_with_index do |s, i|
83
+ if s == 'the'
84
+ split[i] = 'The'
85
+ end
86
+ end
87
+ return split.join(' ')
88
+ end
89
+
90
+ def self.separate_compound_artists(artist)
91
+ if artist.downcase.include?('and')
92
+ Artist.split_artists(artist, 'and')
93
+ elsif artist.downcase.include?('&')
94
+ Artist.split_artists(artist, '&')
95
+ elsif artist.downcase.include?('vs.')
96
+ Artist.split_artists(artist, 'vs.')
97
+ elsif artist.downcase.include?('vs')
98
+ Artist.split_artists(artist, 'vs')
99
+ elsif artist.downcase.include?('with')
100
+ Artist.split_artists(artist, 'with')
101
+ elsif artist.downcase.include?('feat.')
102
+ Artist.split_artists(artist, 'feat.')
103
+ elsif artist.downcase.include?('featuring')
104
+ Artist.split_artists(artist, 'featuring')
105
+ elsif artist.downcase.include?('feat')
106
+ Artist.split_artists(artist, 'feat')
107
+ elsif artist.downcase.include?('presents')
108
+ Artist.split_artists(artist, 'presents')
109
+ else
110
+ [artist]
111
+ end
112
+ end
113
+
114
+ def self.split_artists(string, separator)
115
+ array = string.split(separator)
116
+ new_array = []
117
+ array.each_with_index do |a, i|
118
+ new_array << a.strip
119
+ end
120
+ return new_array
121
+ end
122
+
123
+ end
@@ -0,0 +1,80 @@
1
+ class Barcode
2
+ # Check length
3
+ # Check checksum
4
+
5
+ def self.is_valid?(barcode)
6
+ if Barcode.valid_length?(barcode) && Barcode.valid_characters?(barcode) && Barcode.valid_checksum?(barcode)
7
+ true
8
+ else
9
+ false
10
+ end
11
+ end
12
+
13
+ def self.valid_length?(barcode)
14
+ if barcode.to_s.length == 12 || barcode.to_s.length == 13
15
+ true
16
+ else
17
+ false
18
+ end
19
+ end
20
+
21
+ def self.valid_characters?(barcode)
22
+ number = Integer(barcode) rescue false
23
+ if number != false
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ def self.valid_checksum?(barcode)
31
+ if barcode.to_s[barcode.to_s.length - 1].to_i == Barcode.get_checksum(barcode)
32
+ true
33
+ else
34
+ false
35
+ end
36
+ end
37
+
38
+ def self.get_checksum(barcode)
39
+ barcode_sum = Barcode.calculate_barcode(Barcode.remove_checksum(barcode))
40
+ if barcode_sum != nil
41
+ Barcode.calculate_checksum(barcode_sum)
42
+ else
43
+ nil
44
+ end
45
+ end
46
+
47
+ def self.remove_checksum(barcode)
48
+ barcode = barcode.to_s
49
+ return barcode[0..barcode.length - 2]
50
+ end
51
+
52
+ def self.calculate_barcode(barcode)
53
+ barcode = barcode.to_s
54
+ if barcode.length == 11
55
+ (barcode[0].to_i * 3) + barcode[1].to_i + (barcode[2].to_i * 3) + barcode[3].to_i + (barcode[4].to_i * 3) + barcode[5].to_i + (barcode[6].to_i * 3) + barcode[7].to_i + (barcode[8].to_i * 3) + barcode[9].to_i + (barcode[10].to_i * 3)
56
+ elsif barcode.length == 12
57
+ barcode[0].to_i + (barcode[1].to_i * 3) + barcode[2].to_i + (barcode[3].to_i * 3) + barcode[4].to_i + (barcode[5].to_i * 3) + barcode[6].to_i + (barcode[7].to_i * 3) + barcode[8].to_i + (barcode[9].to_i * 3) + barcode[10].to_i + (barcode[11].to_i * 3)
58
+ else
59
+ nil
60
+ end
61
+ end
62
+
63
+ def self.calculate_checksum(number)
64
+ over = number % 10
65
+ if over != 0
66
+ 10 - over
67
+ else
68
+ 0
69
+ end
70
+ end
71
+
72
+ def self.valid_barcode_or_nil(barcode)
73
+ if Barcode.is_valid?(barcode)
74
+ barcode
75
+ else
76
+ nil
77
+ end
78
+ end
79
+
80
+ end
@@ -0,0 +1,30 @@
1
+ class Composer
2
+
3
+ def self.are_proper_format?(composers)
4
+ if composers && composers != []
5
+ Composer.loop_composers(composers)
6
+ else
7
+ false
8
+ end
9
+ end
10
+
11
+ def self.loop_composers(composers)
12
+ value = true
13
+ composers.each do |c|
14
+ if !Composer.is_proper_format?(c)
15
+ value = false
16
+ break
17
+ end
18
+ end
19
+ value
20
+ end
21
+
22
+ def self.is_proper_format?(composer)
23
+ if Contributor.is_human_name?(composer) && MusicMetadataScore.is_title_case?(composer) && !Artist.is_compound_artist?(composer, false)
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,46 @@
1
+ class Contributor
2
+
3
+ def self.are_proper_format?(contributors)
4
+ if contributors && contributors != []
5
+ Contributor.loop_contributors(contributors)
6
+ else
7
+ false
8
+ end
9
+ end
10
+
11
+ def self.loop_contributors(contributors)
12
+ value = true
13
+ contributors.each do |c|
14
+ if !Contributor.is_proper_format?(c)
15
+ value = false
16
+ break
17
+ end
18
+ end
19
+ value
20
+ end
21
+
22
+ def self.is_proper_format?(contributor)
23
+ if Contributor.is_human_name?(contributor[:name]) && Contributor.has_role?(contributor[:role]) && !Artist.is_compound_artist?(contributor[:name], false) && MusicMetadataScore.is_title_case?(contributor[:name])
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ def self.is_human_name?(contributor)
31
+ if contributor.downcase.include?('the ') || contributor.split(' ').count < 2
32
+ false
33
+ else
34
+ true
35
+ end
36
+ end
37
+
38
+ def self.has_role?(role)
39
+ if role && role != ""
40
+ true
41
+ else
42
+ false
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,55 @@
1
+ class Isrc
2
+
3
+ def self.is_valid?(isrc)
4
+ isrc = Isrc.strip_isrc(isrc)
5
+ if Isrc.valid_length?(isrc) && Isrc.valid_country_code?(isrc[0..1]) && Isrc.valid_numbers?(isrc[5..11])
6
+ true
7
+ else
8
+ false
9
+ end
10
+ end
11
+
12
+ def self.valid_length?(isrc)
13
+ length = Isrc.strip_isrc(isrc).length rescue 0
14
+ if length != 12
15
+ false
16
+ else
17
+ true
18
+ end
19
+ end
20
+
21
+ def self.strip_isrc(isrc)
22
+ if isrc
23
+ isrc.strip!
24
+ isrc.tr!('^A-Za-z0-9', '')
25
+ isrc.upcase!
26
+ end
27
+ return isrc
28
+ end
29
+
30
+ def self.valid_country_code?(code)
31
+ if code.length == 2 && code[/[a-zA-Z]+/] == code
32
+ true
33
+ else
34
+ false
35
+ end
36
+ end
37
+
38
+ def self.valid_numbers?(numbers)
39
+ number = Integer(numbers) rescue false
40
+ if numbers.to_s.length == 7 && number
41
+ true
42
+ else
43
+ false
44
+ end
45
+ end
46
+
47
+ def self.valid_isrc_or_nil(isrc)
48
+ if Isrc.is_valid?(isrc)
49
+ Isrc.strip_isrc(isrc)
50
+ else
51
+ nil
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,30 @@
1
+ class Publisher
2
+
3
+ def self.are_proper_format?(publishers)
4
+ if publishers && publishers != []
5
+ Publisher.loop_publishers(publishers)
6
+ else
7
+ false
8
+ end
9
+ end
10
+
11
+ def self.loop_publishers(publishers)
12
+ value = true
13
+ publishers.each do |c|
14
+ if !Publisher.is_proper_format?(c)
15
+ value = false
16
+ break
17
+ end
18
+ end
19
+ value
20
+ end
21
+
22
+ def self.is_proper_format?(publisher)
23
+ if publisher && publisher != ""
24
+ true
25
+ else
26
+ false
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,234 @@
1
+ class Score
2
+ # Errors
3
+
4
+ # Product level scores
5
+ def self.product_score(metadata)
6
+ score = Score.product_fields_exist(metadata) + Score.product_fields_quality(metadata)
7
+ percent = score.to_f / 87 * 100
8
+ return percent.round(1)
9
+ end
10
+
11
+ def self.product_fields_exist(metadata)
12
+ score = 0
13
+ score += 2 if !Score.object_is_blank?(metadata[:title])
14
+ score += 2 if !Score.object_is_blank?(metadata[:cat_no])
15
+ score += 2 if !Score.object_is_blank?(metadata[:barcode])
16
+ score += 2 if !Score.object_is_blank?(metadata[:label])
17
+ score += 2 if !Score.object_is_blank?(metadata[:primary_artists])
18
+ score += 2 if !Score.object_is_blank?(metadata[:release_date])
19
+ score += 1 if !Score.object_is_blank?(metadata[:original_release_date])
20
+ score += 2 if !Score.object_is_blank?(metadata[:p_year])
21
+ score += 2 if !Score.object_is_blank?(metadata[:p_line])
22
+ score += 2 if !Score.object_is_blank?(metadata[:c_year])
23
+ score += 2 if !Score.object_is_blank?(metadata[:c_line])
24
+ score += 2 if !Score.object_is_blank?(metadata[:parental_advisory])
25
+ score += 1 if !Score.object_is_blank?(metadata[:live])
26
+ score += 1 if !Score.object_is_blank?(metadata[:remastered])
27
+ score += 2 if !Score.object_is_blank?(metadata[:type])
28
+ score += 2 if !Score.object_is_blank?(metadata[:media])
29
+ score += 2 if !Score.object_is_blank?(metadata[:format])
30
+ score += 2 if !Score.object_is_blank?(metadata[:primary_genre])
31
+ score += 1 if !Score.object_is_blank?(metadata[:secondary_genre])
32
+ score += 1 if !Score.object_is_blank?(metadata[:description])
33
+ return score
34
+ end
35
+
36
+ def self.product_fields_quality(metadata)
37
+ score = 0
38
+ score += 10 if TitleCase.is_proper_title_format?(metadata[:title], metadata[:primary_artists], metadata[:featuring_artists])
39
+ score += 10 if MusicMetadataScore.is_title_case?(metadata[:version])
40
+ score += 5 if MusicMetadataScore.valid_barcode?(metadata[:barcode])
41
+ score += 2 if MusicMetadataScore.is_title_case?(metadata[:label])
42
+ score += 5 if MusicMetadataScore.is_title_case?(metadata[:p_line])
43
+ score += 5 if MusicMetadataScore.is_title_case?(metadata[:c_line])
44
+ score += 10 if Artist.are_proper_artist_format?(metadata[:primary_artists], false, :product)
45
+ score += 5 if Artist.are_proper_artist_format?(metadata[:featuring_artists], false, :product)
46
+ return score
47
+ end
48
+
49
+ def self.product_fields_corrected(metadata)
50
+ titles = TitleCase.build_proper_title(metadata[:title], metadata[:version], metadata[:primary_artists], metadata[:featuring_artists])
51
+ {
52
+ :title => titles[:title],
53
+ :version => titles[:version],
54
+ :cat_no => metadata[:cat_no].upcase,
55
+ :barcode => Barcode.valid_barcode_or_nil(metadata[:barcode]),
56
+ :label => TitleCase.title_case(metadata[:label]),
57
+ :primary_artists => Artist.format_artists(metadata[:primary_artists], false, :product),
58
+ :featuring_artists => Artist.format_artists(metadata[:featuring_artists], false, :product),
59
+ :release_date => metadata[:release_date],
60
+ :original_release_date => metadata[:original_release_date],
61
+ :p_year => metadata[:p_year],
62
+ :p_line => TitleCase.title_case(metadata[:p_line]),
63
+ :c_year => metadata[:c_year],
64
+ :c_line => TitleCase.title_case(metadata[:c_line]),
65
+ :parental_advisory => metadata[:parental_advisory],
66
+ :live => metadata[:live],
67
+ :remastered => metadata[:remastered],
68
+ :type => metadata[:type],
69
+ :media => metadata[:media],
70
+ :format => metadata[:format],
71
+ :primary_genre => TitleCase.title_case(metadata[:primary_genre]),
72
+ :secondary_genre => TitleCase.title_case(metadata[:secondary_genre]),
73
+ :description => metadata[:description]
74
+ }
75
+ end
76
+
77
+ # Track level scores
78
+ def self.track_score(metadata)
79
+ score = Score.track_fields_exist(metadata) + Score.track_fields_quality(metadata)
80
+ percent = score.to_f / 107 * 100
81
+ return percent.round(1)
82
+ end
83
+
84
+ def self.track_fields_exist(metadata)
85
+ score = 0
86
+ score += 2 if !Score.object_is_blank?(metadata[:title])
87
+ score += 2 if !Score.object_is_blank?(metadata[:isrc])
88
+ score += 2 if !Score.object_is_blank?(metadata[:primary_artists])
89
+ score += 2 if !Score.object_is_blank?(metadata[:p_year])
90
+ score += 2 if !Score.object_is_blank?(metadata[:p_line])
91
+ score += 2 if !Score.object_is_blank?(metadata[:parental_advisory])
92
+ score += 2 if !Score.object_is_blank?(metadata[:primary_genre])
93
+ score += 2 if !Score.object_is_blank?(metadata[:secondary_genre])
94
+ score += 2 if !Score.object_is_blank?(metadata[:recording_year])
95
+ score += 2 if !Score.object_is_blank?(metadata[:recording_country])
96
+ score += 2 if !Score.object_is_blank?(metadata[:recording_location])
97
+ score += 2 if !Score.object_is_blank?(metadata[:country_of_commission])
98
+ score += 2 if !Score.object_is_blank?(metadata[:rights_holder])
99
+ score += 2 if !Score.object_is_blank?(metadata[:contributors])
100
+ score += 2 if !Score.object_is_blank?(metadata[:composers])
101
+ score += 2 if !Score.object_is_blank?(metadata[:publishers])
102
+ return score
103
+ end
104
+
105
+ def self.track_fields_quality(metadata)
106
+ score = 0
107
+ score += 10 if TitleCase.is_proper_title_format?(metadata[:title], metadata[:primary_artists], metadata[:featuring_artists])
108
+ score += 10 if MusicMetadataScore.is_title_case?(metadata[:version])
109
+ score += 5 if MusicMetadataScore.valid_isrc?(metadata[:isrc])
110
+ score += 5 if MusicMetadataScore.is_title_case?(metadata[:p_line])
111
+ score += 10 if Artist.are_proper_artist_format?(metadata[:primary_artists], false, :product)
112
+ score += 5 if Artist.are_proper_artist_format?(metadata[:featuring_artists], false, :product)
113
+ score += 10 if Contributor.are_proper_format?(metadata[:contributors])
114
+ score += 10 if Composer.are_proper_format?(metadata[:composers])
115
+ score += 10 if Publisher.are_proper_format?(metadata[:publishers])
116
+ return score
117
+ end
118
+
119
+ def self.track_fields_corrected(metadata)
120
+ titles = TitleCase.build_proper_title(metadata[:title], metadata[:version], metadata[:primary_artists], metadata[:featuring_artists])
121
+ {
122
+ :title => titles[:title],
123
+ :version => titles[:version],
124
+ :isrc => Isrc.valid_isrc_or_nil(metadata[:isrc]),
125
+ :primary_artists => Artist.format_artists(metadata[:primary_artists], false, :track),
126
+ :featuring_artists => Artist.format_artists(metadata[:featuring_artists], false, :track),
127
+ :p_year => metadata[:p_year],
128
+ :p_line => TitleCase.title_case(metadata[:p_line]),
129
+ :parental_advisory => metadata[:parental_advisory],
130
+ :primary_genre => TitleCase.title_case(metadata[:primary_genre]),
131
+ :secondary_genre => TitleCase.title_case(metadata[:secondary_genre]),
132
+ :recording_year => metadata[:recording_year],
133
+ :recording_country => metadata[:recording_country],
134
+ :recording_location => metadata[:recording_location],
135
+ :country_of_commission => metadata[:country_of_commission],
136
+ :rights_holder => metadata[:rights_holder],
137
+ :contributors => metadata[:contributors],
138
+ :composers => metadata[:composers],
139
+ :publishers => metadata[:publishers]
140
+ }
141
+ end
142
+
143
+ def self.object_is_blank?(value)
144
+ if value != nil && value != "" && value != [] && value != {}
145
+ false
146
+ else
147
+ true
148
+ end
149
+ end
150
+
151
+ # Total Product Score
152
+ def self.total_product_score(metadata)
153
+ product_score = Score.product_score(metadata[:product])
154
+ tracks_score = Score.tracks_score(metadata[:tracks])
155
+ final_score = (product_score + tracks_score) / (metadata[:tracks].count + 1)
156
+ return final_score.round(1)
157
+ end
158
+
159
+ def self.tracks_score(tracks)
160
+ tracks_score = 0
161
+ if tracks
162
+ tracks.each do |t|
163
+ tracks_score += Score.track_score(t)
164
+ end
165
+ end
166
+ return tracks_score
167
+ end
168
+
169
+ # Corrections
170
+ def self.corrections(metadata)
171
+ array = []
172
+ array = Score.combine_arrays(array, Score.product_corrections(metadata[:product]))
173
+ array = Score.combine_arrays(array, Score.tracks_corrections(metadata[:tracks]))
174
+ end
175
+
176
+ def self.product_corrections(metadata)
177
+ array = []
178
+ corrected_metadata = Score.product_fields_corrected(metadata)
179
+ metadata.each do |m|
180
+ error = Score.missing_or_corrected(m[0].to_s, m[1], corrected_metadata[m[0]])
181
+ array << error if error
182
+ end
183
+ array
184
+ end
185
+
186
+ def self.tracks_corrections(tracks)
187
+ array = []
188
+ tracks.each_with_index do |t, index|
189
+ track_returned = Score.track_corrections(t)
190
+ track_returned.each do |tr|
191
+ array << "Track #{index + 1} #{tr}"
192
+ end
193
+ end
194
+ array
195
+ end
196
+
197
+ def self.track_corrections(metadata)
198
+ array = []
199
+ corrected_metadata = Score.track_fields_corrected(metadata)
200
+ metadata.each do |m|
201
+ error = Score.missing_or_corrected(m[0].to_s, m[1], corrected_metadata[m[0]])
202
+ array << error if error
203
+ end
204
+ array
205
+ end
206
+
207
+ def self.missing_or_corrected(field, original, corrected)
208
+ if original && original != "" && original != [] || original == false
209
+ if original == corrected || corrected == ''
210
+ nil
211
+ else
212
+ "#{Score.format_field_name(field)} is incorrect. It should be #{corrected}"
213
+ end
214
+ else
215
+ "#{Score.format_field_name(field)} is missing"
216
+ end
217
+ end
218
+
219
+ def self.format_field_name(field)
220
+ split = field.split('_')
221
+ split.each do |s|
222
+ s = TitleCase.titleize(s)
223
+ end
224
+ split.join(' ')
225
+ end
226
+
227
+ def self.combine_arrays(first, second)
228
+ second.each do |s|
229
+ first << s
230
+ end
231
+ first
232
+ end
233
+
234
+ end
@@ -0,0 +1,330 @@
1
+ class TitleCase
2
+ # Split string
3
+ # Capitalize all
4
+ # Find list of words
5
+ # If they're not first or last, or have a bracket on the beginning or end make em lower case
6
+
7
+ def self.featuring_words
8
+ [
9
+ ' Featuring',
10
+ 'Feat ',
11
+ ' Feat.',
12
+ ' With '
13
+
14
+ ]
15
+ end
16
+
17
+ def self.title_case(string)
18
+ if string.is_a?(String)
19
+ string = TitleCase.add_spacing_to_punctuation(string)
20
+ array = TitleCase.lower_case_array(TitleCase.titleize_array(TitleCase.split_string(string.downcase)))
21
+ new_string = TitleCase.capitalize_artist_the(array.join(' '))
22
+ return TitleCase.capitalize_anomalies(new_string)
23
+ else
24
+ ""
25
+ end
26
+ end
27
+
28
+ # Add spaces after any punctuation if it doesn't already exist
29
+ def self.add_spacing_to_punctuation(string)
30
+ newstring = ""
31
+ string.split('').each_with_index do |letter, i|
32
+ newstring += ' ' if TitleCase.add_space_before?(string, letter, i)
33
+ newstring += letter
34
+ newstring += ' ' if TitleCase.add_space_after?(string, letter, i)
35
+ end
36
+ return newstring
37
+ end
38
+
39
+ def self.add_space_after?(string, letter, i)
40
+ if letter =~ /[,.)]/ && string[i + 1] != ' '
41
+ true
42
+ elsif letter == '"'
43
+ if string[i - 1] =~ /[[:alpha:]]/ && string[i + 1] != ' '
44
+ true
45
+ end
46
+ else
47
+ false
48
+ end
49
+ end
50
+
51
+ def self.add_space_before?(string, letter, i)
52
+ if letter =~ /[(]/ && string[i - 1] != ' '
53
+ true
54
+ else
55
+ false
56
+ end
57
+ end
58
+
59
+ # String to Array
60
+ def self.split_string(string)
61
+ if string && string.is_a?(String)
62
+ string.split(' ')
63
+ else
64
+ []
65
+ end
66
+ end
67
+
68
+ # Capitalize all first letters
69
+ def self.titleize_array(array)
70
+ array.each do |a|
71
+ a = TitleCase.titleize(a)
72
+ end
73
+ return array
74
+ end
75
+
76
+ def self.titleize(string)
77
+ if string[0] =~ /[[:alpha:]]/
78
+ string.capitalize!
79
+ elsif string.length > 1 && !string.include?('feat.')
80
+ string[1] = string[1].capitalize
81
+ end
82
+ return string
83
+ end
84
+
85
+ # Pick words for lower case
86
+ def self.lower_case_array(array)
87
+ array.each do |a|
88
+ if a != array.first && a != array.last
89
+ a = TitleCase.lower_case_string(a)
90
+ end
91
+ end
92
+ return array
93
+ end
94
+
95
+ def self.lower_case_string(string)
96
+ #a, an, and, as, at, but, by, for, from, in, into, nor, of, off, on, onto, or, out, over, so, the, to, up, with and yet
97
+ words = ["a", "an", "and", "as", "at", "but", "by", "for", "from", "in", "into", "nor", "of", "off", "on", "onto", "or", "out", "over", "so", "the", "to", "up", "with", "yet"]
98
+ if words.include?(string.downcase)
99
+ string.downcase!
100
+ else
101
+ string
102
+ end
103
+ end
104
+
105
+ # Includes Featuring Artistst
106
+ def self.is_proper_title_format?(title, primary_artists, featuring_artists)
107
+ if MusicMetadataScore.is_title_case?(title) && TitleCase.includes_proper_featuring_artists?(title, featuring_artists) && !TitleCase.includes_version?(title) && !TitleCase.improper_abbreviations?(title)
108
+ true
109
+ else
110
+ false
111
+ end
112
+ end
113
+
114
+ def self.includes_proper_featuring_artists?(title, featuring_artists)
115
+ count = featuring_artists.count rescue 0
116
+ if count > 0
117
+ TitleCase.is_proper_featuring_artists?(title, featuring_artists)
118
+ else
119
+ true
120
+ end
121
+ end
122
+
123
+ def self.includes_featuring_artists?(title, featuring_artists)
124
+ value = false
125
+ featuring_artists.each do |a|
126
+ if title.include?(a)
127
+ value = true
128
+ break
129
+ end
130
+ end
131
+ value
132
+ end
133
+
134
+ def self.is_proper_featuring_artists?(title, featuring_artists)
135
+ if title.include?(TitleCase.featuring_artists_string(featuring_artists, "feat.")) || title.include?(TitleCase.featuring_artists_string(featuring_artists, "with")) || title.include?(TitleCase.featuring_artists_string(featuring_artists, "vs.")) || title.include?(TitleCase.featuring_artists_string(featuring_artists, "Meets"))
136
+ true
137
+ else
138
+ false
139
+ end
140
+ end
141
+
142
+ def self.featuring_artists_string(featuring_artists, type)
143
+ string = ""
144
+ if featuring_artists.count > 0
145
+ string = " (#{type} " if type != 'vs.'
146
+ string = TitleCase.add_artists_to_string(string, featuring_artists, type)
147
+ string += ")"
148
+ end
149
+ return string
150
+ end
151
+
152
+ def self.add_artists_to_string(string, featuring_artists, type)
153
+ featuring_artists.each do |a|
154
+ string += a
155
+ string += TitleCase.featuring_artists_join(featuring_artists, a, type)
156
+ end
157
+ return string
158
+ end
159
+
160
+ def self.featuring_artists_join(featuring_artists, a, type)
161
+ if featuring_artists.count == 1 || a == featuring_artists.last
162
+ ""
163
+ elsif featuring_artists.index(a) == featuring_artists.count - 2 && type != "vs."
164
+ " & "
165
+ elsif type == "vs."
166
+ " vs. "
167
+ elsif type == "Meets"
168
+ " Meets "
169
+ else
170
+ ", "
171
+ end
172
+ end
173
+
174
+ def self.combined_artists(primary_artists, featuring_artists)
175
+ featuring_artists.each do |a|
176
+ primary_artists << a
177
+ end
178
+ primary_artists
179
+ end
180
+
181
+ # Includes a Version
182
+ def self.includes_version?(title)
183
+ if title.downcase.include?('version') || title.downcase.include?('edition') || title.downcase.include?('issue') || title.downcase.include?('print') || title.downcase.include?('anniversary')
184
+ true
185
+ else
186
+ false
187
+ end
188
+ end
189
+
190
+ # Check for abbreviations
191
+ def self.improper_abbreviations?(title)
192
+ # TODO find pt. and vol. not formatted properly
193
+ if title.downcase.include?(' part') || title.downcase.include?(' volume') || title.downcase.include?(' number') || title.downcase.include?(' aka') || title.downcase.include?(' d.j') || title.downcase.include?(' num')
194
+ true
195
+ else
196
+ false
197
+ end
198
+ end
199
+
200
+ def self.replace_improper_abbreviations(title)
201
+ title.gsub!(" part", " Pt.")
202
+ title.gsub!(" volume", " Vol.")
203
+ title.gsub!(" number", " No.")
204
+ title.gsub!(" no ", " No. ")
205
+ title.gsub!(" num ", " No. ")
206
+ title.gsub!(" d.j", " DJ")
207
+ title.gsub!(" d.j.", " DJ")
208
+ title.gsub!(" aka", " a.k.a")
209
+ title.gsub!(" AKA", " a.k.a")
210
+ title.gsub!("featuring", "feat.")
211
+ title.gsub!("(feat ", "(feat. ")
212
+ title.gsub!(" feat ", " feat. ")
213
+ title.gsub!(" vs ", " Vs. ")
214
+ title.gsub!(" VS ", " Vs. ")
215
+
216
+ return title
217
+ end
218
+
219
+ # Capitalize The's in Artist Names
220
+ def self.capitalize_artist_the(title)
221
+ if title[0] != '(' && title.include?('(')
222
+ split_title = title.split('(')
223
+ inside_bracket = split_title[1].split(')')
224
+ new_inside_bracket = inside_bracket[0].gsub('the', 'The')
225
+ string = split_title[0] + "(" + new_inside_bracket + ")"
226
+ string += inside_bracket[1] if inside_bracket[1]
227
+ return string
228
+ else
229
+ return title
230
+ end
231
+ end
232
+
233
+ # Capitalize any anomalies, like DJ
234
+ def self.capitalize_anomalies(title)
235
+ title.gsub('Dj ', 'DJ ')
236
+ end
237
+
238
+ # Get Value Inside Brackets
239
+ def self.get_inside_brackets(string)
240
+ if string.include?('(') && string.include?(')')
241
+ split_string = string.split('(')
242
+ array = []
243
+ split_string.each do |s|
244
+ stripped = s.strip
245
+ if stripped[stripped.length - 1] == ')'
246
+ array << stripped[0..stripped.length - 2]
247
+ end
248
+ end
249
+ array
250
+ end
251
+ end
252
+
253
+ # Build proper title
254
+ def self.build_proper_title(title, version, primary_artists, featuring_artists)
255
+ # Title Case
256
+ title = TitleCase.title_case(title)
257
+ # find any versions
258
+ split_out_version = TitleCase.strip_version_from_title(title, version)
259
+ # reformat featuring artists
260
+ title = TitleCase.strip_artists_from_title(split_out_version[:title], primary_artists, featuring_artists)
261
+ title += TitleCase.featuring_artists_string(featuring_artists, "feat.")
262
+ # replace abbreviations
263
+ return {:title => title, :version => split_out_version[:version]}
264
+ end
265
+
266
+ def self.strip_version_from_title(title, version)
267
+ inside_brackets = TitleCase.get_inside_brackets(title)
268
+ #version = nil
269
+ if inside_brackets
270
+ inside_brackets.each do |b|
271
+ if TitleCase.includes_version?(b)
272
+ title.gsub!(" (#{b})", "")
273
+ version = b.strip
274
+ end
275
+ end
276
+ end
277
+ return {
278
+ :title => title.strip,
279
+ :version => version
280
+ }
281
+ end
282
+
283
+ def self.strip_artists_from_title(title, primary_artists, featuring_artists)
284
+ title = TitleCase.strip_artists_in_brackets_from_title(title, primary_artists, featuring_artists)
285
+ title = TitleCase.strip_artists_from_outside_brackets_from_title(title, primary_artists, featuring_artists)
286
+ title
287
+ end
288
+
289
+ def self.strip_artists_in_brackets_from_title(title, primary_artists, featuring_artists)
290
+ inside_brackets = TitleCase.get_inside_brackets(title)
291
+ if inside_brackets
292
+ inside_brackets.each do |b|
293
+ if TitleCase.includes_featuring_artists?(b, featuring_artists)
294
+ title.gsub!(" (#{b})", "")
295
+ end
296
+ end
297
+ end
298
+ title
299
+ end
300
+
301
+ def self.strip_artists_from_outside_brackets_from_title(title, primary_artists, featuring_artists)
302
+ title = TitleCase.remove_artist_names_from_title(title, TitleCase.combined_artists(primary_artists, featuring_artists))
303
+ TitleCase.strip_featuring_words_from_title(title)
304
+ end
305
+
306
+ def self.remove_artist_names_from_title(title, artists)
307
+ artists.each do |a|
308
+ if title.include?(" #{a} &")
309
+ title.gsub!(" #{a} &", "")
310
+ elsif title.include?(" #{a} and")
311
+ title.gsub!(" #{a} and", "")
312
+ elsif title.include?(" #{a} And")
313
+ title.gsub!(" #{a} And", "")
314
+ elsif title.include?(" #{a},")
315
+ title.gsub!(" #{a},", "")
316
+ else
317
+ title.gsub!(" #{a}", "")
318
+ end
319
+ end
320
+ title
321
+ end
322
+
323
+ def self.strip_featuring_words_from_title(title)
324
+ TitleCase.featuring_words.each do |w|
325
+ title.gsub!(" #{w.strip}", "")
326
+ end
327
+ title
328
+ end
329
+
330
+ end
@@ -0,0 +1,3 @@
1
+ module MusicMetadataScore
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,46 @@
1
+ require "music_metadata_score/version"
2
+ require "music_metadata_score/score"
3
+ require "music_metadata_score/title_case"
4
+ require "music_metadata_score/barcode"
5
+ require "music_metadata_score/isrc"
6
+ require "music_metadata_score/artist"
7
+ require "music_metadata_score/contributor"
8
+ require "music_metadata_score/composer"
9
+ require "music_metadata_score/publisher"
10
+
11
+ module MusicMetadataScore
12
+
13
+ # Title Casing
14
+ def self.title_case(string)
15
+ TitleCase.title_case(string)
16
+ end
17
+
18
+ def self.is_title_case?(string)
19
+ proper = TitleCase.title_case(string)
20
+ if proper == string
21
+ true
22
+ else
23
+ false
24
+ end
25
+ end
26
+
27
+ # Barcode
28
+ def self.valid_barcode?(barcode)
29
+ Barcode.is_valid?(barcode)
30
+ end
31
+
32
+ def self.generate_checksum(barcode)
33
+ barcode_sum = Barcode.calculate_barcode(barcode)
34
+ if barcode_sum != nil
35
+ Barcode.calculate_checksum(barcode_sum)
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ # ISRC
42
+ def self.valid_isrc?(isrc)
43
+ Isrc.is_valid?(isrc)
44
+ end
45
+
46
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: music_metadata_score
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - tomallen400
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: ''
42
+ email:
43
+ - tomallen4000@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.md
49
+ - Gemfile
50
+ - Rakefile
51
+ - lib/music_metadata_score.rb
52
+ - lib/music_metadata_score/artist.rb
53
+ - lib/music_metadata_score/barcode.rb
54
+ - lib/music_metadata_score/composer.rb
55
+ - lib/music_metadata_score/contributor.rb
56
+ - lib/music_metadata_score/isrc.rb
57
+ - lib/music_metadata_score/publisher.rb
58
+ - lib/music_metadata_score/score.rb
59
+ - lib/music_metadata_score/title_case.rb
60
+ - lib/music_metadata_score/version.rb
61
+ homepage: ''
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.0.3
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: ''
85
+ test_files: []