musicfix 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.
- 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: []
|