musicfix 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +23 -0
- data/README +91 -0
- data/bin/musicfix +277 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9f17741eecd6fd928c9cc4dd4a23f413d4df4f37
|
4
|
+
data.tar.gz: 9e8d84a8aad5c677fa2a1de96b935575d391ca39
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2289ce8b5a494a1a9bdf817af6d7506fc6721523aa0c83c2a0c70d2801b8d280cb9a7fd7946c19e40bf3b0d1a418d1f2615a4d53cf94e93941e8c19234fbf3ef
|
7
|
+
data.tar.gz: ff5ce233fe598ac27e0d23119b289dd4eea27a5640bf47c70ed07a28f1f9eb1e7bbb13bbbdc57a9dada2ee7c220e7f4aa2c5a44dc045bb3854b9c2cc01bda9a3
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2012--2013 Lazaros Koromilas <lostd@2f30.org>
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions
|
6
|
+
are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
12
|
+
documentation and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
15
|
+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
16
|
+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
17
|
+
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
18
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
19
|
+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
20
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
21
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
22
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
23
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# $Id: README,v 1.11 2013/02/02 09:44:52 lostd Exp $
|
2
|
+
|
3
|
+
# Description
|
4
|
+
|
5
|
+
Musicfix is a music file renamer and tagger with consistency concerns.
|
6
|
+
It has a release-centered logic and gets data from the Discogs.com site.
|
7
|
+
First configure the target music directory (`mdir` config attribute).
|
8
|
+
Next browse the site and locate your album. Change to the album's
|
9
|
+
directory. Ensure the files are ordered and provide the release id
|
10
|
+
to the program. Enjoy!
|
11
|
+
|
12
|
+
|
13
|
+
# Dependencies
|
14
|
+
|
15
|
+
* System packages: taglib
|
16
|
+
* Ruby gems: discogs-wrapper stringex taglib-ruby
|
17
|
+
|
18
|
+
|
19
|
+
# Usage
|
20
|
+
|
21
|
+
$ musicfix relid [tracks]
|
22
|
+
$ musicfix dump relid [relfile]
|
23
|
+
$ musicfix load [relfile]
|
24
|
+
|
25
|
+
More sophisticated track selection can be performed by providing
|
26
|
+
a second command line argument of the form:
|
27
|
+
|
28
|
+
"1--3,5,8"
|
29
|
+
"A2--A4,B3"
|
30
|
+
"1-8--1-9,1-10--1-11"
|
31
|
+
|
32
|
+
Alternatively, YAML metadata files can be used (`release.yaml` by default)
|
33
|
+
in order to dump information from Discogs.com and/or load the information
|
34
|
+
from file. This way you can generate a base release file, edit to
|
35
|
+
your needs, and then use it for tagging and renaming.
|
36
|
+
|
37
|
+
|
38
|
+
# Configuration
|
39
|
+
|
40
|
+
The simplest `~/.musicfixrc` configuration file only contains:
|
41
|
+
|
42
|
+
mdir: ~/music
|
43
|
+
|
44
|
+
Furthermore, templates are used for the naming of music files
|
45
|
+
and the cover artwork image. The defaults are:
|
46
|
+
|
47
|
+
track: '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{d}#{fn}-#{fa}-#{ft}.#{x}"'
|
48
|
+
image: '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{zz}-#{fba}-#{fb}_cover.jpg"'
|
49
|
+
|
50
|
+
To run a shell command upon completion use the `after` attribute.
|
51
|
+
For example, to update the MPD database:
|
52
|
+
|
53
|
+
after: '"mpc update #{fba}-#{my}-#{fb}-#{fv}"'
|
54
|
+
|
55
|
+
The variables used in the templates are explained below.
|
56
|
+
The 'f' prefix means "normalized for filename".
|
57
|
+
|
58
|
+
Use everywhere:
|
59
|
+
|
60
|
+
[f]b: album name
|
61
|
+
[f]ba: album artist
|
62
|
+
y: release year
|
63
|
+
my: master release year
|
64
|
+
g: genre style
|
65
|
+
[f]v: release format
|
66
|
+
|
67
|
+
Only for track naming:
|
68
|
+
|
69
|
+
[f]a: track artist
|
70
|
+
[f]t: track title
|
71
|
+
[f]n: track number, may have letters (vinyls)
|
72
|
+
d: disc number
|
73
|
+
tn: track number counter
|
74
|
+
x: file extension in lowercase
|
75
|
+
|
76
|
+
Only for image naming:
|
77
|
+
|
78
|
+
zz: zeros that match d + n width
|
79
|
+
|
80
|
+
|
81
|
+
# Some test releases
|
82
|
+
|
83
|
+
* Sound, The (2) – From The Lions Mouth: 377432
|
84
|
+
* Various – Return Of The Banshee: 127565
|
85
|
+
* Harold Budd & Brian Eno – The Pearl: 699395
|
86
|
+
* Dead Souls Rising – Clepsydre 1993-1999: 1212954
|
87
|
+
* Metro Decay – Υπέρβαση: 1766242
|
88
|
+
* Dispossessed, The (2) – Sister Mary: 1965566
|
89
|
+
* Einstürzende Neubauten – Strategies Against Architecture IV: 2506503
|
90
|
+
* David Bowie – The Man Who Sold The World: 484622
|
91
|
+
* Various – Some Bizzare Album: 1381202
|
data/bin/musicfix
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# $Id: musicfix,v 1.30 2013/06/13 23:09:01 lostd Exp $
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'open-uri'
|
9
|
+
require 'discogs'
|
10
|
+
require 'stringex'
|
11
|
+
require 'taglib'
|
12
|
+
require 'yaml'
|
13
|
+
|
14
|
+
# Concatenate artist list using '&'
|
15
|
+
# Convert "Sound, The (2)" to "The Sound"
|
16
|
+
def mkartist al
|
17
|
+
nl = al.collect {|a| a.name}
|
18
|
+
nl.each {|n| n.gsub! /\s\(\d+\)$/, ''}
|
19
|
+
nl.each {|n| n.gsub! /(.*), The$/, 'The \1'}
|
20
|
+
nl.join ' & '
|
21
|
+
end
|
22
|
+
|
23
|
+
# Convert "3" to (nil, "03")
|
24
|
+
# Convert "A" to (nil, "A0")
|
25
|
+
# Convert "A3" to (nil, "A3")
|
26
|
+
# Convert "2.3" to ("2", "03")
|
27
|
+
# Convert "2.03" to ("2", "03")
|
28
|
+
# Convert "CD2-3" to ("2", "03")
|
29
|
+
def mkdiscnum pos
|
30
|
+
np = pos.gsub /[-.]/, '#'
|
31
|
+
d, n = np.split '#'
|
32
|
+
if not n
|
33
|
+
n = d
|
34
|
+
d = nil
|
35
|
+
else
|
36
|
+
d = d.gsub /\D/, ''
|
37
|
+
end
|
38
|
+
if n.match /[A-Z]/
|
39
|
+
n = n.ljust(2, '0')
|
40
|
+
else
|
41
|
+
n = n.rjust(2, '0')
|
42
|
+
end
|
43
|
+
return d, n
|
44
|
+
end
|
45
|
+
|
46
|
+
# Add wordings for symbols
|
47
|
+
Stringex.localize_from :en => {"≠" => "not equals"}
|
48
|
+
|
49
|
+
# Convert to lowercase ASCII without punctuation
|
50
|
+
def mkname n
|
51
|
+
n = n.gsub '12"', '12 inch'
|
52
|
+
n = n.gsub "12''", "12 inch"
|
53
|
+
n = n.gsub "12'", "12 inch"
|
54
|
+
n = n.gsub '7"', '7 inch'
|
55
|
+
n = n.gsub "7''", "7 inch"
|
56
|
+
n = n.gsub "7'", "7 inch"
|
57
|
+
n = n.gsub " & ", " and "
|
58
|
+
n.to_url.gsub '-', '_'
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get cover artwork
|
62
|
+
def getimgurl rel
|
63
|
+
return nil unless rel.images
|
64
|
+
img = rel.images.select {|i| i.type == 'primary'}.first ||
|
65
|
+
rel.images.first
|
66
|
+
img.uri.gsub 'api.discogs.com', 's.pixogs.com'
|
67
|
+
end
|
68
|
+
|
69
|
+
# Construct position list from tracks filter
|
70
|
+
# The string "1--3,5,8" becomes ["1", "2", "3", "5", "8"]
|
71
|
+
# The string "A2--A4,B3" becomes ["A2", "A3", "A4", "B3"]
|
72
|
+
# The string "1-8--1-9,1-10--1-11" becomes ["1-8", "1-9", "1-10", "1-11"]
|
73
|
+
# Returns nil on error
|
74
|
+
def mkposlist tracks
|
75
|
+
pl = tracks.split(",").map do |r|
|
76
|
+
r = r.split("--")
|
77
|
+
return nil if r.include? ""
|
78
|
+
r = case r.length
|
79
|
+
when 1 then r
|
80
|
+
when 2 then (r[0] .. r[1]).map {|i| i}
|
81
|
+
else return nil
|
82
|
+
end
|
83
|
+
return nil if r.empty?
|
84
|
+
r
|
85
|
+
end
|
86
|
+
pl.flatten
|
87
|
+
end
|
88
|
+
|
89
|
+
# Parse command line
|
90
|
+
usage = ''
|
91
|
+
usage << "Usage: musicfix relid [tracks]\n"
|
92
|
+
usage << " musicfix dump relid [relfile]\n"
|
93
|
+
usage << " musicfix load [relfile]\n"
|
94
|
+
cmd = ARGV[0] || (puts usage; exit)
|
95
|
+
case cmd
|
96
|
+
when 'load' then
|
97
|
+
relfile = ARGV[1] || 'release.yaml'
|
98
|
+
when 'dump' then
|
99
|
+
relid = ARGV[1] || (puts usage; exit)
|
100
|
+
relfile = ARGV[2] || 'release.yaml'
|
101
|
+
else
|
102
|
+
relid = ARGV[0]
|
103
|
+
tracks = ARGV[1] || nil
|
104
|
+
end
|
105
|
+
|
106
|
+
# Default configuration
|
107
|
+
cfg = {}
|
108
|
+
cfg['mdir'] = '/var/music'
|
109
|
+
cfg['track'] = '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{d}#{fn}-#{fa}-#{ft}.#{x}"'
|
110
|
+
cfg['image'] = '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{zz}-#{fba}-#{fb}_cover.jpg"'
|
111
|
+
#cfg['after'] = '"mpc update #{fba}-#{my}-#{fb}-#{fv}"'
|
112
|
+
|
113
|
+
# User configuration overrides
|
114
|
+
cfgpath = File.expand_path('~/.musicfixrc')
|
115
|
+
if File.exists? cfgpath
|
116
|
+
new = YAML.load File.open(cfgpath, 'r')
|
117
|
+
cfg.merge! new
|
118
|
+
end
|
119
|
+
|
120
|
+
# Expand music directory
|
121
|
+
cfg['mdir'] = File.expand_path cfg['mdir']
|
122
|
+
|
123
|
+
# Print configuration
|
124
|
+
puts 'Configuration'
|
125
|
+
puts cfg.to_yaml
|
126
|
+
|
127
|
+
unless cmd == 'dump' then
|
128
|
+
# Supported formats
|
129
|
+
fmtre = /mp3|ogg|m4a|mpc|flac|wv/i
|
130
|
+
# Construct file list
|
131
|
+
fl = Dir['*'].select {|f| File.extname(f).match fmtre}.sort
|
132
|
+
if fl.empty? then
|
133
|
+
puts 'No music files found!'
|
134
|
+
exit
|
135
|
+
end
|
136
|
+
# Output file list
|
137
|
+
puts 'Files to process'
|
138
|
+
puts fl.to_yaml
|
139
|
+
end
|
140
|
+
|
141
|
+
# Initialize release info
|
142
|
+
if cmd == 'load' then
|
143
|
+
# Load release data from file
|
144
|
+
if File.exists? relfile then
|
145
|
+
puts "Loading release data..."
|
146
|
+
rel = YAML.load File.open(relfile, 'r')
|
147
|
+
else
|
148
|
+
puts "Release file #{relfile} not found."
|
149
|
+
exit
|
150
|
+
end
|
151
|
+
else
|
152
|
+
# Get release data from Discogs
|
153
|
+
puts "Geting release data..."
|
154
|
+
discogs = Discogs::Wrapper.new("musicfix")
|
155
|
+
r = discogs.get_release(relid)
|
156
|
+
mr = if r.master_id then discogs.get_master_release(r.master_id) end
|
157
|
+
# Tracklist can contain dummy header tracks, strip them
|
158
|
+
tl = r.tracklist.select {|t| t.position}
|
159
|
+
# Apply tracks filter if given
|
160
|
+
if tracks then
|
161
|
+
pl = mkposlist tracks
|
162
|
+
puts "Invalid tracks filter: #{tracks}" unless pl
|
163
|
+
tl = tl.select {|t| pl.include? t.position}
|
164
|
+
end
|
165
|
+
# Gather release-wide data
|
166
|
+
rel = {}
|
167
|
+
rel['artist'] = mkartist r.artists
|
168
|
+
rel['album'] = r.title
|
169
|
+
rel['year'] = r.released
|
170
|
+
rel['masteryear'] = if mr then mr.year end || r.released
|
171
|
+
# Year can be full-date so keep only the year part
|
172
|
+
rel['year'] = rel['year'].slice(0..3)
|
173
|
+
rel['masteryear'] = rel['masteryear'].slice(0..3)
|
174
|
+
rel['genre'] = if r.styles then r.styles.first end ||
|
175
|
+
if r.genres then r.genres.first end
|
176
|
+
rel['format'] = r.formats.first.name
|
177
|
+
rel['comment'] = "Discogs: #{r.id}"
|
178
|
+
rel['imgurl'] = getimgurl r
|
179
|
+
rel['tracklist'] = []
|
180
|
+
# Populate tracklist
|
181
|
+
tl.each do |s|
|
182
|
+
trk = {}
|
183
|
+
trk['pos'] = s.position
|
184
|
+
trk['artist'] = mkartist s.artists if s.artists
|
185
|
+
trk['title'] = s.title
|
186
|
+
rel['tracklist'] << trk
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Output release info
|
191
|
+
puts rel.to_yaml
|
192
|
+
if cmd == 'dump' then
|
193
|
+
File.open(relfile, 'w') do |f|
|
194
|
+
f.puts rel.to_yaml
|
195
|
+
end
|
196
|
+
exit
|
197
|
+
end
|
198
|
+
|
199
|
+
# Variables for use in templates
|
200
|
+
mdir = cfg['mdir']
|
201
|
+
ba = rel['artist']
|
202
|
+
b = rel['album']
|
203
|
+
y = rel['year']
|
204
|
+
my = rel['masteryear']
|
205
|
+
g = rel['genre']
|
206
|
+
v = rel['format']
|
207
|
+
c = rel['comment']
|
208
|
+
fba = mkname ba
|
209
|
+
fb = mkname b
|
210
|
+
fv = mkname v
|
211
|
+
# Internal use only
|
212
|
+
tl = rel['tracklist']
|
213
|
+
|
214
|
+
# Sanity checks
|
215
|
+
if tl.length != fl.length then
|
216
|
+
puts "Found #{tl.length} tracks for #{fl.length} music files."
|
217
|
+
if fl.length < tl.length then
|
218
|
+
# Limit entries to number of files
|
219
|
+
tl = tl.first fl.length
|
220
|
+
print "Use only the first #{fl.length} entries? [y/N] "
|
221
|
+
else
|
222
|
+
# Limit files to available tracks
|
223
|
+
fl = fl.first tl.length
|
224
|
+
print "Use only the first #{fl.length} files? [y/N] "
|
225
|
+
end
|
226
|
+
res = STDIN.readline.strip
|
227
|
+
exit unless res == 'y'
|
228
|
+
end
|
229
|
+
|
230
|
+
# Loop over the music files and
|
231
|
+
# 1. Copy them over with proper names
|
232
|
+
# 2. Fix the tags on the new files
|
233
|
+
tn = 0
|
234
|
+
fl.each do |ofname|
|
235
|
+
tn = tn.next
|
236
|
+
trk = tl[tn - 1]
|
237
|
+
# Use track artist for compilations, fallback to release
|
238
|
+
a = trk['artist'] || rel['artist']
|
239
|
+
d, n = mkdiscnum trk['pos'].to_s
|
240
|
+
t = trk['title']
|
241
|
+
fa = mkname a
|
242
|
+
ft = mkname t
|
243
|
+
fn = mkname n
|
244
|
+
x = File.extname(ofname).delete('.').downcase
|
245
|
+
nfname = eval cfg['track']
|
246
|
+
puts "Copy track to #{nfname}"
|
247
|
+
FileUtils.makedirs(File.dirname nfname)
|
248
|
+
FileUtils.copy(ofname, nfname)
|
249
|
+
TagLib::FileRef.open(nfname) do |f|
|
250
|
+
f.tag.artist = a
|
251
|
+
f.tag.album = b
|
252
|
+
f.tag.title = t
|
253
|
+
f.tag.track = tn
|
254
|
+
f.tag.year = y.to_i
|
255
|
+
f.tag.genre = g
|
256
|
+
f.tag.comment = c
|
257
|
+
f.save
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Also save the first image of the artwork
|
262
|
+
zz = '0' * (mkdiscnum tl.last['pos'].to_s).join.length
|
263
|
+
imgname = eval cfg['image']
|
264
|
+
if rel['imgurl'] then
|
265
|
+
puts "Save cover to #{imgname}"
|
266
|
+
img = open(rel['imgurl'], {"User-Agent" => "Mozilla"}).read
|
267
|
+
File.open(imgname, 'wb').write img
|
268
|
+
end
|
269
|
+
|
270
|
+
# Execute command if provided
|
271
|
+
if cfg['after'] then
|
272
|
+
puts "Executing finishing command"
|
273
|
+
run = eval cfg['after']
|
274
|
+
`#{run}`
|
275
|
+
end
|
276
|
+
|
277
|
+
# vim:set ts=4 sw=4 et:
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: musicfix
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lazaros Koromilas
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: discogs-wrapper
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: stringex
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: taglib-ruby
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: 'Musicfix is a music file renamer and tagger with consistency concerns. '
|
56
|
+
email: lostd@2f30.org
|
57
|
+
executables:
|
58
|
+
- musicfix
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- bin/musicfix
|
63
|
+
- README
|
64
|
+
- LICENSE
|
65
|
+
homepage: http://www.2f30.org/~lostd/musicfix
|
66
|
+
licenses:
|
67
|
+
- BSD
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 2.0.0
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: Music file renamer and tagger using Discogs.com
|
89
|
+
test_files: []
|