sweeper 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/CHANGELOG +2 -0
- data/Manifest +1 -0
- data/README +22 -8
- data/TODO +3 -0
- data/bin/sweeper +13 -7
- data/lib/sweeper.rb +117 -28
- data/sweeper.gemspec +7 -6
- data/test/integration/sweeper_test.rb +16 -5
- data/vendor/lastfm.fpclient.beta2.OSX-intel/lastfmfpclient +0 -0
- data/vendor/lastfm.fpclient.beta2.OSX-intel/libcurl.dylib +0 -0
- data/vendor/lastfm.fpclient.beta2.OSX-intel/libmad.dylib +0 -0
- data/vendor/lastfm.fpclient.beta2.OSX-intel/libsamplerate.dylib +0 -0
- data/vendor/lastfm.fpclient.beta2.OSX-intel/libtag.dylib +0 -0
- data/vendor/lastfm.fpclient.beta2.linux-32/lastfmfpclient +0 -0
- metadata +13 -2
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG
CHANGED
data/Manifest
CHANGED
data/README
CHANGED
@@ -3,7 +3,7 @@ Automatically tag your music collection with metadata from Last.fm.
|
|
3
3
|
|
4
4
|
== License
|
5
5
|
|
6
|
-
Copyright 2008 Cloudburst, LLC. Licensed under the AFL 3. See the included LICENSE file. Unlinked portions copyright 2008 Last.fm Ltd., used by permission, and licensed under the
|
6
|
+
Copyright 2008 Cloudburst, LLC. Licensed under the AFL 3. See the included LICENSE file. Unlinked portions copyright 2008 Last.fm Ltd., used by permission, and licensed under the GPL 2.
|
7
7
|
|
8
8
|
The public certificate for the gem is here[http://rubyforge.org/frs/download.php/25331/evan_weaver-original-public_cert.pem].
|
9
9
|
|
@@ -11,15 +11,27 @@ If you use this software, please {make a donation}[http://blog.evanweaver.com/do
|
|
11
11
|
|
12
12
|
== Features
|
13
13
|
|
14
|
-
*
|
15
|
-
*
|
14
|
+
* faster than MusicBrainz
|
15
|
+
* better results than MusicBrainz
|
16
|
+
* smart genre tagging
|
16
17
|
|
17
|
-
|
18
|
+
<b>WARNING: This is beta software. Backup your music collection first.</b>
|
19
|
+
|
20
|
+
== Requirements
|
21
|
+
|
22
|
+
* Linux, Intel OS X, or Windows
|
23
|
+
* Ruby and Rubygems
|
24
|
+
* id3lib (Linux and OS X only)
|
25
|
+
* some mp3 files
|
18
26
|
|
19
|
-
|
27
|
+
== Installation
|
28
|
+
|
29
|
+
Just install the gem:
|
20
30
|
sudo gem install sweeper
|
21
31
|
|
22
|
-
|
32
|
+
== Usage
|
33
|
+
|
34
|
+
Change to the directory of mp3s you want to tag and run:
|
23
35
|
sweeper
|
24
36
|
|
25
37
|
That's all.
|
@@ -31,8 +43,10 @@ Sweeper takes a few command line options:
|
|
31
43
|
-d, --dir Directory to search (defaults to current).
|
32
44
|
-r, --recursive Recurse directories.
|
33
45
|
--dry-run Do a dry run (no files will be changed).
|
34
|
-
-f, --force Overwrite existing tags.
|
35
|
-
|
46
|
+
-f, --force Overwrite all existing tags.
|
47
|
+
-g, --genre Add genre and genre comments.
|
48
|
+
-e, --force-genre Add genre and genre comments, overwriting e
|
49
|
+
|
36
50
|
== Reporting problems
|
37
51
|
|
38
52
|
The support forum is here[http://rubyforge.org/forum/forum.php?forum_id=23599].
|
data/TODO
ADDED
data/bin/sweeper
CHANGED
@@ -19,7 +19,7 @@ Choice.options do
|
|
19
19
|
desc 'Recurse directories.'
|
20
20
|
end
|
21
21
|
|
22
|
-
option :dry do
|
22
|
+
option :"dry-run" do
|
23
23
|
long '--dry-run'
|
24
24
|
desc 'Do a dry run (no files will be changed).'
|
25
25
|
end
|
@@ -27,14 +27,20 @@ Choice.options do
|
|
27
27
|
option :force do
|
28
28
|
short '-f'
|
29
29
|
long '--force'
|
30
|
-
desc 'Overwrite existing tags.'
|
30
|
+
desc 'Overwrite all existing tags.'
|
31
31
|
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
option :genre do
|
34
|
+
short '-g'
|
35
|
+
long '--genre'
|
36
|
+
desc "Add genre and genre comments."
|
37
|
+
end
|
38
|
+
|
39
|
+
option :"force-genre" do
|
40
|
+
short '-e'
|
41
|
+
long '--force-genre'
|
42
|
+
desc "Add genre and genre comments, overwriting existing ones."
|
43
|
+
end
|
38
44
|
end
|
39
45
|
|
40
46
|
Sweeper.new(Choice.choices).run
|
data/lib/sweeper.rb
CHANGED
@@ -3,31 +3,59 @@ require 'rubygems'
|
|
3
3
|
require 'id3lib'
|
4
4
|
require 'xsd/mapping'
|
5
5
|
require 'activesupport'
|
6
|
+
require 'open-uri'
|
7
|
+
require 'uri'
|
8
|
+
require 'amatch'
|
6
9
|
|
7
10
|
class ID3Lib::Tag
|
8
|
-
|
9
|
-
|
11
|
+
def url
|
12
|
+
f = frame(:WORS)
|
13
|
+
f ? f[:url] : nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def url=(s)
|
17
|
+
remove_frame(:WORS)
|
18
|
+
self << {:id => :WORS, :url => s} if s.any?
|
19
|
+
end
|
10
20
|
end
|
11
21
|
|
12
22
|
class Sweeper
|
13
23
|
|
14
24
|
class Problem < RuntimeError; end
|
15
25
|
|
16
|
-
|
26
|
+
BASIC_KEYS = ['artist', 'title', 'url']
|
27
|
+
GENRE_KEYS = ['genre', 'comment']
|
28
|
+
GENRES = ID3Lib::Info::Genres.sort
|
29
|
+
GENRE_COUNT = 7
|
30
|
+
DEFAULT_GENRE = {'genre' => 'Other', 'comment' => 'other'}
|
17
31
|
|
18
32
|
attr_reader :options
|
19
33
|
|
20
34
|
def initialize(options = {})
|
35
|
+
options['genre'] ||= options['force-genre']
|
21
36
|
@dir = File.expand_path(options['dir'] || Dir.pwd)
|
22
37
|
@options = options
|
23
38
|
end
|
24
39
|
|
25
|
-
def run
|
26
|
-
@
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
40
|
+
def run
|
41
|
+
@read = 0
|
42
|
+
@updated = 0
|
43
|
+
@failed = 0
|
44
|
+
|
45
|
+
Kernel.at_exit do
|
46
|
+
if @read == 0
|
47
|
+
puts "No files found. Maybe you meant --recursive?"
|
48
|
+
exec "#{$0} --help"
|
49
|
+
else
|
50
|
+
puts "Read: #{@read}\nUpdated: #{@updated}\nFailed: #{@failed}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
begin
|
55
|
+
recurse(@dir)
|
56
|
+
rescue Object => e
|
57
|
+
puts "Unknown error: #{e.inspect}"
|
58
|
+
ENV['DEBUG'] ? raise : exit
|
31
59
|
end
|
32
60
|
end
|
33
61
|
|
@@ -38,19 +66,21 @@ class Sweeper
|
|
38
66
|
if File.directory? filename and options['recursive']
|
39
67
|
recurse(filename)
|
40
68
|
elsif File.extname(filename) == ".mp3"
|
41
|
-
@
|
69
|
+
@read += 1
|
42
70
|
tries = 0
|
43
71
|
begin
|
44
|
-
current = read(filename)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
72
|
+
current = read(filename)
|
73
|
+
updated = lookup(filename, current)
|
74
|
+
|
75
|
+
if updated != current
|
76
|
+
write(filename, updated)
|
77
|
+
@updated += 1
|
50
78
|
end
|
79
|
+
|
51
80
|
rescue Problem => e
|
52
81
|
tries += 1 and retry if tries < 2
|
53
|
-
puts "Skipped #{filename}: #{e.message}"
|
82
|
+
puts "Skipped #{File.basename(filename)}: #{e.message}"
|
83
|
+
@failed += 1
|
54
84
|
end
|
55
85
|
end
|
56
86
|
end
|
@@ -58,44 +88,103 @@ class Sweeper
|
|
58
88
|
|
59
89
|
def read(filename)
|
60
90
|
tags = {}
|
61
|
-
|
91
|
+
|
92
|
+
song = ID3Lib::Tag.new(filename, ID3Lib::V2)
|
93
|
+
if song.empty?
|
94
|
+
song = ID3Lib::Tag.new(filename, ID3Lib::V1)
|
95
|
+
end
|
62
96
|
|
63
|
-
|
97
|
+
(BASIC_KEYS + GENRE_KEYS).each do |key|
|
64
98
|
tags[key] = song.send(key) if !song.send(key).blank?
|
65
99
|
end
|
100
|
+
|
66
101
|
tags
|
67
102
|
end
|
68
103
|
|
69
|
-
def lookup(filename)
|
104
|
+
def lookup(filename, tags = {})
|
105
|
+
updated = {}
|
106
|
+
if options['force'] or
|
107
|
+
(BASIC_KEYS - tags.keys).any?
|
108
|
+
updated.merge!(lookup_basic(filename))
|
109
|
+
end
|
110
|
+
if options['genre'] and
|
111
|
+
(options['force'] or options['force-genre'] or (GENRE_KEYS - tags.keys).any?)
|
112
|
+
updated.merge!(lookup_genre(updated.merge(tags)))
|
113
|
+
end
|
114
|
+
|
115
|
+
if options['force']
|
116
|
+
tags.merge!(updated)
|
117
|
+
elsif options['force-genre']
|
118
|
+
tags.merge!(updated.slice('genre', 'comment'))
|
119
|
+
end
|
120
|
+
|
121
|
+
updated.merge(tags)
|
122
|
+
end
|
123
|
+
|
124
|
+
def lookup_basic(filename)
|
70
125
|
Dir.chdir File.dirname(binary) do
|
71
|
-
response = silence { `./#{File.basename(binary)} #{filename}` }
|
126
|
+
response = silence { `./#{File.basename(binary)} #{filename.inspect}` }
|
72
127
|
object = begin
|
73
128
|
XSD::Mapping.xml2obj(response)
|
74
129
|
rescue REXML::ParseException
|
75
|
-
raise Problem, "
|
130
|
+
raise Problem, "Server sent invalid response."
|
76
131
|
end
|
77
|
-
raise Problem, "
|
132
|
+
raise Problem, "Fingerprint failed or not found." unless object
|
78
133
|
|
79
134
|
tags = {}
|
80
135
|
song = Array(object.track).first
|
81
136
|
|
82
|
-
|
137
|
+
BASIC_KEYS.each do |key|
|
83
138
|
tags[key] = song.send(key) if song.respond_to? key
|
84
139
|
end
|
85
140
|
tags
|
86
141
|
end
|
87
142
|
end
|
88
143
|
|
144
|
+
def lookup_genre(tags)
|
145
|
+
return DEFAULT_GENRE if tags['artist'].blank?
|
146
|
+
|
147
|
+
response = begin
|
148
|
+
open("http://ws.audioscrobbler.com/1.0/artist/#{URI.encode(tags['artist'])}/toptags.xml").read
|
149
|
+
rescue OpenURI::HTTPError
|
150
|
+
return DEFAULT_GENRE
|
151
|
+
end
|
152
|
+
|
153
|
+
object = XSD::Mapping.xml2obj(response)
|
154
|
+
return DEFAULT_GENRE if !object.respond_to? :tag
|
155
|
+
|
156
|
+
genres = Array(object.tag)[0..(GENRE_COUNT - 1)].map(&:name)
|
157
|
+
return DEFAULT_GENRE if !genres.any?
|
158
|
+
|
159
|
+
primary = nil
|
160
|
+
genres.each do |this|
|
161
|
+
match_results = Amatch::Levenshtein.new(this).similar(GENRES)
|
162
|
+
max = match_results.max
|
163
|
+
match = GENRES[match_results.index(max)]
|
164
|
+
|
165
|
+
if ['Rock', 'Pop', 'Rap'].include? match
|
166
|
+
# Penalize useless genres
|
167
|
+
max = max / 3.0
|
168
|
+
end
|
169
|
+
|
170
|
+
if !primary or primary.first < max
|
171
|
+
primary = [max, match]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
{'genre' => primary.last, 'comment' => genres.join(" ")}
|
176
|
+
end
|
177
|
+
|
89
178
|
def write(filename, tags)
|
90
179
|
return if tags.empty?
|
91
|
-
puts filename
|
180
|
+
puts File.basename(filename)
|
92
181
|
|
93
|
-
file = ID3Lib::Tag.new(filename)
|
182
|
+
file = ID3Lib::Tag.new(filename, ID3Lib::V2)
|
94
183
|
tags.each do |key, value|
|
95
184
|
file.send("#{key}=", value)
|
96
|
-
puts " #{value}"
|
185
|
+
puts " #{key.capitalize}: #{value}"
|
97
186
|
end
|
98
|
-
file.update! unless options['dry']
|
187
|
+
file.update! unless options['dry-run']
|
99
188
|
end
|
100
189
|
|
101
190
|
def binary
|
data/sweeper.gemspec
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
|
2
|
-
# Gem::Specification for Sweeper-0.
|
2
|
+
# Gem::Specification for Sweeper-0.2
|
3
3
|
# Originally generated by Echoe
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = %q{sweeper}
|
7
|
-
s.version = "0.
|
7
|
+
s.version = "0.2"
|
8
8
|
|
9
9
|
s.specification_version = 2 if s.respond_to? :specification_version=
|
10
10
|
|
@@ -16,19 +16,20 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.email = %q{}
|
17
17
|
s.executables = ["sweeper"]
|
18
18
|
s.extensions = ["ext/extconf.rb"]
|
19
|
-
s.extra_rdoc_files = ["CHANGELOG", "lib/sweeper.rb", "LICENSE", "README"]
|
20
|
-
s.files = ["bin/sweeper", "CHANGELOG", "ext/extconf.rb", "ext/Makefile", "lib/sweeper.rb", "LICENSE", "Manifest", "README", "test/integration/sweeper_test.rb", "vendor/lastfm.fpclient.beta2.linux-32/lastfmfpclient", "vendor/lastfm.fpclient.beta2.linux-32/readme.txt", "vendor/lastfm.fpclient.beta2.OSX-intel/lastfmfpclient", "vendor/lastfm.fpclient.beta2.OSX-intel/libcurl.dylib", "vendor/lastfm.fpclient.beta2.OSX-intel/libmad.dylib", "vendor/lastfm.fpclient.beta2.OSX-intel/libsamplerate.dylib", "vendor/lastfm.fpclient.beta2.OSX-intel/libtag.dylib", "vendor/lastfm.fpclient.beta2.OSX-intel/readme.txt", "vendor/lastfm.fpclient.beta2.win32/lastfmfpclient.exe", "vendor/lastfm.fpclient.beta2.win32/libcurl.dll", "vendor/lastfm.fpclient.beta2.win32/libfftw3f-3.dll", "vendor/lastfm.fpclient.beta2.win32/Microsoft.VC80.CRT.manifest", "vendor/lastfm.fpclient.beta2.win32/msvcm80.dll", "vendor/lastfm.fpclient.beta2.win32/msvcp80.dll", "vendor/lastfm.fpclient.beta2.win32/msvcr80.dll", "vendor/lastfm.fpclient.beta2.win32/taglib.dll", "vendor/lastfm.fpclient.beta2.win32/zlib1.dll", "sweeper.gemspec"]
|
19
|
+
s.extra_rdoc_files = ["CHANGELOG", "lib/sweeper.rb", "LICENSE", "README", "TODO"]
|
20
|
+
s.files = ["bin/sweeper", "CHANGELOG", "ext/extconf.rb", "ext/Makefile", "lib/sweeper.rb", "LICENSE", "Manifest", "README", "test/integration/sweeper_test.rb", "TODO", "vendor/lastfm.fpclient.beta2.linux-32/lastfmfpclient", "vendor/lastfm.fpclient.beta2.linux-32/readme.txt", "vendor/lastfm.fpclient.beta2.OSX-intel/lastfmfpclient", "vendor/lastfm.fpclient.beta2.OSX-intel/libcurl.dylib", "vendor/lastfm.fpclient.beta2.OSX-intel/libmad.dylib", "vendor/lastfm.fpclient.beta2.OSX-intel/libsamplerate.dylib", "vendor/lastfm.fpclient.beta2.OSX-intel/libtag.dylib", "vendor/lastfm.fpclient.beta2.OSX-intel/readme.txt", "vendor/lastfm.fpclient.beta2.win32/lastfmfpclient.exe", "vendor/lastfm.fpclient.beta2.win32/libcurl.dll", "vendor/lastfm.fpclient.beta2.win32/libfftw3f-3.dll", "vendor/lastfm.fpclient.beta2.win32/Microsoft.VC80.CRT.manifest", "vendor/lastfm.fpclient.beta2.win32/msvcm80.dll", "vendor/lastfm.fpclient.beta2.win32/msvcp80.dll", "vendor/lastfm.fpclient.beta2.win32/msvcr80.dll", "vendor/lastfm.fpclient.beta2.win32/taglib.dll", "vendor/lastfm.fpclient.beta2.win32/zlib1.dll", "sweeper.gemspec"]
|
21
21
|
s.has_rdoc = true
|
22
22
|
s.homepage = %q{http://blog.evanweaver.com/files/doc/fauna/sweeper/}
|
23
23
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Sweeper", "--main", "README"]
|
24
24
|
s.require_paths = ["lib", "ext"]
|
25
25
|
s.rubyforge_project = %q{fauna}
|
26
|
-
s.rubygems_version = %q{1.0
|
26
|
+
s.rubygems_version = %q{1.1.0}
|
27
27
|
s.summary = %q{Automatically tag your music collection with metadata from Last.fm.}
|
28
28
|
s.test_files = ["test/integration/sweeper_test.rb"]
|
29
29
|
|
30
30
|
s.add_dependency(%q<id3lib-ruby>, [">= 0"])
|
31
31
|
s.add_dependency(%q<choice>, [">= 0"])
|
32
|
+
s.add_dependency(%q<amatch>, [">= 0"])
|
32
33
|
s.add_dependency(%q<activesupport>, [">= 0"])
|
33
34
|
end
|
34
35
|
|
@@ -43,7 +44,7 @@ end
|
|
43
44
|
# p.summary = "Automatically tag your music collection with metadata from Last.fm."
|
44
45
|
# p.url = "http://blog.evanweaver.com/files/doc/fauna/sweeper/"
|
45
46
|
# p.docs_host = "blog.evanweaver.com:~/www/bax/public/files/doc/"
|
46
|
-
# p.dependencies = ['id3lib-ruby', 'choice', 'activesupport']
|
47
|
+
# p.dependencies = ['id3lib-ruby', 'choice', 'amatch', 'activesupport']
|
47
48
|
# p.clean_pattern = ['doc', 'pkg', 'test/integration/songs']
|
48
49
|
# p.rdoc_pattern = ['README', 'LICENSE', 'CHANGELOG', 'TODO', 'lib/*']
|
49
50
|
# end
|
@@ -11,25 +11,36 @@ class SweeperTest < Test::Unit::TestCase
|
|
11
11
|
@found_many = "#{@dir}/1_001.mp3"
|
12
12
|
@found_one = "#{@dir}/1_010.mp3"
|
13
13
|
@not_found = "#{@dir}/1_003.mp3"
|
14
|
-
@s = Sweeper.new('dir' => @dir)
|
14
|
+
@s = Sweeper.new('dir' => @dir, 'genre' => true)
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
17
|
+
def test_lookup_basic
|
18
18
|
assert_equal(
|
19
19
|
{"artist"=>"Photon Band",
|
20
20
|
"title"=>"To Sing For You",
|
21
21
|
"url"=>"http://www.last.fm/music/Photon+Band/_/To+Sing+For+You"},
|
22
|
-
@s.
|
22
|
+
@s.lookup_basic(@found_many))
|
23
23
|
assert_equal(
|
24
24
|
{"artist"=>"Various Artists - Vagabond Productions",
|
25
25
|
"title"=>"Sugar Man - Tom Heyman",
|
26
26
|
"url"=> "http://www.last.fm/music/Various+Artists+-+Vagabond+Productions/_/Sugar+Man+-+Tom+Heyman"},
|
27
|
-
@s.
|
27
|
+
@s.lookup_basic(@found_one))
|
28
28
|
assert_raises(Sweeper::Problem) do
|
29
|
-
@s.
|
29
|
+
@s.lookup_basic(@not_found)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
def test_lookup_genre
|
34
|
+
assert_equal(
|
35
|
+
{"genre"=>"Psychadelic", "comment"=>"rock psychedelic mod Philly"},
|
36
|
+
@s.lookup_genre(@s.lookup_basic(@found_many))
|
37
|
+
)
|
38
|
+
assert_equal(
|
39
|
+
Sweeper::DEFAULT_GENRE,
|
40
|
+
@s.lookup_genre({})
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
33
44
|
def test_read
|
34
45
|
assert_equal({},
|
35
46
|
@s.read(@found_many))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sweeper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.2"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Weaver
|
@@ -51,6 +51,15 @@ dependencies:
|
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: "0"
|
53
53
|
version:
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: amatch
|
56
|
+
version_requirement:
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
54
63
|
- !ruby/object:Gem::Dependency
|
55
64
|
name: activesupport
|
56
65
|
version_requirement:
|
@@ -71,6 +80,7 @@ extra_rdoc_files:
|
|
71
80
|
- lib/sweeper.rb
|
72
81
|
- LICENSE
|
73
82
|
- README
|
83
|
+
- TODO
|
74
84
|
files:
|
75
85
|
- bin/sweeper
|
76
86
|
- CHANGELOG
|
@@ -81,6 +91,7 @@ files:
|
|
81
91
|
- Manifest
|
82
92
|
- README
|
83
93
|
- test/integration/sweeper_test.rb
|
94
|
+
- TODO
|
84
95
|
- vendor/lastfm.fpclient.beta2.linux-32/lastfmfpclient
|
85
96
|
- vendor/lastfm.fpclient.beta2.linux-32/readme.txt
|
86
97
|
- vendor/lastfm.fpclient.beta2.OSX-intel/lastfmfpclient
|
@@ -127,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
138
|
requirements: []
|
128
139
|
|
129
140
|
rubyforge_project: fauna
|
130
|
-
rubygems_version: 1.0
|
141
|
+
rubygems_version: 1.1.0
|
131
142
|
signing_key:
|
132
143
|
specification_version: 2
|
133
144
|
summary: Automatically tag your music collection with metadata from Last.fm.
|
metadata.gz.sig
CHANGED
Binary file
|