musicfix 0.1.7 → 0.2.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 +4 -4
- data/CHANGELOG +5 -0
- data/README +17 -14
- data/bin/musicfix +137 -69
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b01d3bf3f2c2fc81008328a152fdf80a191041d
|
4
|
+
data.tar.gz: 66e75cc847b3e4d355ef3d37d8e76748b1a597ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed3f4f00107c13f7d86d0f2b90646dfa5e2af129361719f0e10104c7a0fca9380c6534307741fa4ff944935703f503a10200f828b7fab4a6473adec1ae13f454
|
7
|
+
data.tar.gz: 3a3a4ace3d9e9b0c5a0b0b8d36da4da7bdb33604e30db9e6319e04eacbaba6bdc2ab36e803ec77541b699edacc300ee15359e96462104f4a173cc719aa64f71c
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 0.2.0
|
2
|
+
* Fix zero padding in numbering and filename order.
|
3
|
+
* Switch to HTTPS and use Discogs Auth through user tokens.
|
4
|
+
* Get images only through the API as intended.
|
5
|
+
|
1
6
|
## 0.1.7
|
2
7
|
* Fix http requests to work with the current API.
|
3
8
|
* All stdout output is valid YAML.
|
data/README
CHANGED
@@ -68,7 +68,7 @@ containing:
|
|
68
68
|
Furthermore, templates are used for the naming of music files, the
|
69
69
|
cover artwork image and the release metadata file. The defaults are:
|
70
70
|
|
71
|
-
track: '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{
|
71
|
+
track: '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{fd}#{n}-#{fa}-#{ft}.#{x}"'
|
72
72
|
image: '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{zz}-#{fba}-#{fb}_cover.jpg"'
|
73
73
|
rdata: '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{zz}-#{fba}-#{fb}_release.yaml"'
|
74
74
|
|
@@ -93,24 +93,27 @@ Only for track naming:
|
|
93
93
|
|
94
94
|
[f]a: track artist
|
95
95
|
[f]t: track title
|
96
|
-
[f]
|
97
|
-
|
98
|
-
tn: track number counter
|
96
|
+
[f]d: disc number or side letter for vinyls
|
97
|
+
n: track number, zero padded
|
99
98
|
x: file extension in lowercase
|
100
99
|
|
101
100
|
Only for image and rdata naming:
|
102
101
|
|
103
102
|
zz: zeros that match d + n width
|
104
103
|
|
104
|
+
Only for image naming:
|
105
105
|
|
106
|
-
|
106
|
+
i: the index in the list
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
108
|
+
|
109
|
+
# Cover artwork support
|
110
|
+
|
111
|
+
From March 2015 and onwards cover artwork images are resources restricted
|
112
|
+
to authenticated clients. The easiest way to do this and the one supported
|
113
|
+
by musicfix is to create a Discogs account and generate a personal access
|
114
|
+
token from the developers settings. Add the token in `~/.musicfixrc` like
|
115
|
+
this:
|
116
|
+
|
117
|
+
token: abcxyz123456
|
118
|
+
|
119
|
+
You'll get no cover artwork without adding your personal access token.
|
data/bin/musicfix
CHANGED
@@ -9,7 +9,7 @@ require 'taglib'
|
|
9
9
|
require 'yaml'
|
10
10
|
|
11
11
|
# Headers
|
12
|
-
Ver = '0.
|
12
|
+
Ver = '0.2.0'
|
13
13
|
Homepage = 'http://git.2f30.org/musicfix/'
|
14
14
|
Headers = {'User-Agent' => "musicfix/#{Ver} +#{Homepage}"}
|
15
15
|
|
@@ -25,8 +25,8 @@ def mkartist al
|
|
25
25
|
end
|
26
26
|
|
27
27
|
# Convert "3" to (nil, "03")
|
28
|
-
# Convert "A" to (
|
29
|
-
# Convert "A3" to (
|
28
|
+
# Convert "A" to ("A", "1")
|
29
|
+
# Convert "A3" to ("A", "3")
|
30
30
|
# Convert "2.3" to ("2", "03")
|
31
31
|
# Convert "2.03" to ("2", "03")
|
32
32
|
# Convert "CD2-3" to ("2", "03")
|
@@ -39,8 +39,10 @@ def mkdiscnum pos
|
|
39
39
|
else
|
40
40
|
d = d.gsub /\D/, ''
|
41
41
|
end
|
42
|
-
|
43
|
-
|
42
|
+
parts = n.match /([A-Z])([0-9]*)/
|
43
|
+
if parts then
|
44
|
+
d = parts[1]
|
45
|
+
n = parts[2] != "" && parts[2] || "1"
|
44
46
|
else
|
45
47
|
n = n.rjust(2, '0')
|
46
48
|
end
|
@@ -48,11 +50,16 @@ def mkdiscnum pos
|
|
48
50
|
end
|
49
51
|
|
50
52
|
# Add wordings for symbols
|
51
|
-
syms = {"≠" => "not equals"
|
53
|
+
syms = {"≠" => "not equals",
|
54
|
+
"Χ" => "x",
|
55
|
+
"★" => "blackstar",
|
56
|
+
"•" => ""}
|
52
57
|
Stringex::Localization.store_translations :en, :transliterations, syms
|
53
58
|
|
54
59
|
# Convert to lowercase ASCII without punctuation
|
55
60
|
# Convert "Jean-Michel Jarre" to "jean_michel_jarre"
|
61
|
+
# Convert "Aaah...!" to "aaah"
|
62
|
+
# Convert "Ruh / Spirit" to "ruh_spirit"
|
56
63
|
def mkname n
|
57
64
|
# These may be in track titles like "(7'' Version)"
|
58
65
|
n = n.gsub '12"', '12 inch'
|
@@ -65,17 +72,20 @@ def mkname n
|
|
65
72
|
n = n.gsub "7''", "7 inch"
|
66
73
|
n = n.gsub "7'", "7 inch"
|
67
74
|
n = n.gsub " & ", " and "
|
75
|
+
n = n.gsub '.', ' '
|
76
|
+
n = n.gsub '/', ' '
|
68
77
|
# Transliterate
|
69
78
|
n.to_url.gsub '-', '_'
|
70
79
|
end
|
71
80
|
|
72
81
|
# Get cover artwork
|
73
|
-
def
|
82
|
+
def getimages rel
|
74
83
|
return nil unless rel['images']
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
84
|
+
imgs = []
|
85
|
+
rel['images'].each do |img|
|
86
|
+
imgs << img['uri']
|
87
|
+
end
|
88
|
+
imgs
|
79
89
|
end
|
80
90
|
|
81
91
|
# Construct position list from tracks filter
|
@@ -98,57 +108,69 @@ def mkposlist tracks
|
|
98
108
|
pl.flatten
|
99
109
|
end
|
100
110
|
|
111
|
+
# Formats we care about and their abbreviations
|
112
|
+
# http://www.discogs.com/search/#more_facets_format_exact
|
113
|
+
# Ignore "Vinyl" in favor of "LP"/"EP"/'7"'/'10"'/'12"' descriptions
|
114
|
+
# Ignore "File" in favor of "MP3"/"WAV"/"FLAC" descriptions
|
115
|
+
# Avoid too generic descriptions such as "Album"
|
116
|
+
# Avoid too specific descriptions such as "Green", "Gatefold"
|
117
|
+
@ft = {
|
118
|
+
'CD' => 'CD',
|
119
|
+
'CDr' => 'CDr',
|
120
|
+
'LP' => 'LP',
|
121
|
+
'EP' => 'EP',
|
122
|
+
'Cassette' => 'Cass',
|
123
|
+
'12"' => '12inch',
|
124
|
+
'10"' => '10inch',
|
125
|
+
'7"' => '7inch',
|
126
|
+
'Mini-Album' => 'Mini',
|
127
|
+
'Maxi-Single' => 'Maxi',
|
128
|
+
'Picture Disc' => 'Pic',
|
129
|
+
'Flexi-disc' => 'Flexi',
|
130
|
+
'Promo' => 'Promo',
|
131
|
+
'Reissue' => 'RE',
|
132
|
+
'Remastered' => 'RM',
|
133
|
+
'Remaster' => 'RM',
|
134
|
+
'Repress' => 'RP',
|
135
|
+
'Mispress' => 'MP',
|
136
|
+
'Test Pressing' => 'TP',
|
137
|
+
'Enhanced' => 'Enh',
|
138
|
+
'Digipak ' => 'Dig',
|
139
|
+
'Box Set' => 'Box',
|
140
|
+
'Limited Edition' => 'Ltd',
|
141
|
+
'Club Edition' => 'Club',
|
142
|
+
'Compilation' => 'Comp',
|
143
|
+
'Sampler' => 'Smplr',
|
144
|
+
'Numbered' => 'Num',
|
145
|
+
'Unofficial Release' => 'Unofficial',
|
146
|
+
'Single Sided' => 'S/Sided',
|
147
|
+
'MP3' => 'MP3',
|
148
|
+
'AAC' => 'AAC',
|
149
|
+
'FLAC' => 'FLAC',
|
150
|
+
'WAV' => 'WAV',
|
151
|
+
}
|
152
|
+
|
101
153
|
# Make a sane format string also using format description
|
102
154
|
def mkformat format
|
103
155
|
f = []
|
104
|
-
|
105
|
-
|
106
|
-
|
156
|
+
formats = []
|
157
|
+
if format['name'] then
|
158
|
+
formats << format['name']
|
107
159
|
end
|
108
|
-
if format['descriptions']
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
160
|
+
if format['descriptions'] then
|
161
|
+
formats += format['descriptions']
|
162
|
+
end
|
163
|
+
formats.each do |d|
|
164
|
+
f << d if @ft.keys.include? d
|
114
165
|
end
|
115
166
|
f.join ' '
|
116
167
|
end
|
117
168
|
|
118
169
|
# Shorten certain common words that appear in format strings
|
119
170
|
def mkshort n
|
120
|
-
ft = {
|
121
|
-
'Cassette' => 'Cass',
|
122
|
-
'12"' => '12inch',
|
123
|
-
'10"' => '10inch',
|
124
|
-
'7"' => '7inch',
|
125
|
-
'Mini-Album' => 'MiniAlbum',
|
126
|
-
'Maxi-Single' => 'Maxi',
|
127
|
-
'Picture Disc' => 'Pic',
|
128
|
-
'Flexi-disc' => 'Flexi',
|
129
|
-
'Reissue' => 'RE',
|
130
|
-
'Remastered' => 'RM',
|
131
|
-
'Remaster' => 'RM',
|
132
|
-
'Repress' => 'RP',
|
133
|
-
'Mispress' => 'MP',
|
134
|
-
'Test Pressing' => 'TP',
|
135
|
-
'Enhanced' => 'Enh',
|
136
|
-
'Digipak ' => 'Dig',
|
137
|
-
'Box Set' => 'Box',
|
138
|
-
'Limited Edition' => 'Ltd',
|
139
|
-
'Club Edition' => 'Club',
|
140
|
-
'Compilation' => 'Comp',
|
141
|
-
'Sampler' => 'Smplr',
|
142
|
-
'Numbered' => 'Num',
|
143
|
-
'Unofficial Release' => 'Unofficial',
|
144
|
-
'Single Sided' => 'S/Sided',
|
145
|
-
'File MP3' => 'MP3',
|
146
|
-
'File FLAC' => 'FLAC',
|
147
|
-
'File WAV' => 'WAV',
|
148
|
-
}
|
149
171
|
# Note that prefix substitution is broken
|
150
|
-
ftre = /(#{ft.keys.join('|')})/
|
151
|
-
n.gsub(ftre, ft)
|
172
|
+
ftre = /(#{@ft.keys.join('|')})/
|
173
|
+
n.gsub(ftre, @ft)
|
152
174
|
end
|
153
175
|
|
154
176
|
# Return single item if array is full of duplicates
|
@@ -185,10 +207,11 @@ end
|
|
185
207
|
# Default configuration
|
186
208
|
cfg = {}
|
187
209
|
cfg['mdir'] = '~/music'
|
188
|
-
cfg['track'] = '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{
|
189
|
-
cfg['image'] = '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{zz}-#{fba}-#{fb}_cover.jpg"'
|
210
|
+
cfg['track'] = '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{fd}#{n}-#{fa}-#{ft}.#{x}"'
|
211
|
+
cfg['image'] = '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{zz}-#{fba}-#{fb}_cover#{i}.jpg"'
|
190
212
|
cfg['rdata'] = '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{zz}-#{fba}-#{fb}_release.yaml"'
|
191
213
|
#cfg['after'] = '"mpc update #{fba}-#{my}-#{fb}-#{fv}"'
|
214
|
+
cfg['nimg'] = 1
|
192
215
|
|
193
216
|
# User configuration overrides
|
194
217
|
cfgpath = File.expand_path('~/.musicfixrc')
|
@@ -197,6 +220,12 @@ if File.exists? cfgpath
|
|
197
220
|
cfg.merge! new
|
198
221
|
end
|
199
222
|
|
223
|
+
# Authentication option
|
224
|
+
urlopts = ''
|
225
|
+
if cfg['token'] then
|
226
|
+
urlopts = "?token=#{cfg['token']}"
|
227
|
+
end
|
228
|
+
|
200
229
|
# Expand music directory
|
201
230
|
cfg['mdir'] = File.expand_path cfg['mdir']
|
202
231
|
|
@@ -214,7 +243,7 @@ end
|
|
214
243
|
|
215
244
|
unless cmd == 'dump' then
|
216
245
|
# Supported formats
|
217
|
-
fmtre = /mp3|ogg|m4a|mpc|flac|wv/i
|
246
|
+
fmtre = /mp3|ogg|m4a|mpc|flac|wv|wav|aiff/i
|
218
247
|
# Construct file list
|
219
248
|
fl = Dir['*'].select {|f| File.extname(f).match fmtre}.sort
|
220
249
|
if fl.empty? then
|
@@ -262,7 +291,7 @@ elsif cmd == 'tags' then
|
|
262
291
|
rel['genre'] = []
|
263
292
|
rel['format'] = nil
|
264
293
|
rel['comment'] = []
|
265
|
-
rel['
|
294
|
+
rel['images'] = nil
|
266
295
|
rel['tracklist'] = []
|
267
296
|
# Populate tracklist
|
268
297
|
fl.each do |fname|
|
@@ -300,10 +329,10 @@ elsif cmd == 'tags' then
|
|
300
329
|
else
|
301
330
|
# Get release data from Discogs
|
302
331
|
STDERR.puts "Getting release data from Discogs..."
|
303
|
-
r = YAML.load(open("
|
332
|
+
r = YAML.load(open("https://api.discogs.com/releases/#{relid}#{urlopts}",
|
304
333
|
Headers))
|
305
334
|
mr = if r['master_id'] then
|
306
|
-
YAML.load(open("
|
335
|
+
YAML.load(open("https://api.discogs.com/masters/#{r['master_id']}#{urlopts}",
|
307
336
|
Headers))
|
308
337
|
end
|
309
338
|
# Tracklist can contain dummy header tracks, strip them
|
@@ -337,7 +366,8 @@ else
|
|
337
366
|
if tracks then
|
338
367
|
rel['comment'] += ", tracks: #{tracks}"
|
339
368
|
end
|
340
|
-
|
369
|
+
imgs = getimages(r)
|
370
|
+
rel['images'] = if imgs then imgs.first(cfg['nimg']) end
|
341
371
|
rel['tracklist'] = []
|
342
372
|
# Populate tracklist
|
343
373
|
tl.each do |s|
|
@@ -393,6 +423,21 @@ if tl.length != fl.length then
|
|
393
423
|
exit unless res == 'y'
|
394
424
|
end
|
395
425
|
|
426
|
+
# First pass decides zero padding and file numbering
|
427
|
+
zpad_disc = 0
|
428
|
+
zpad_num = 0
|
429
|
+
tl.each do |trk|
|
430
|
+
disc, num = mkdiscnum trk['pos'].to_s
|
431
|
+
if zpad_disc < disc.length then zpad_disc = disc.length end
|
432
|
+
if zpad_num < num.length then zpad_num = num.length end
|
433
|
+
trk['disc'] = disc
|
434
|
+
trk['num'] = num
|
435
|
+
end
|
436
|
+
tl.each do |trk|
|
437
|
+
trk['disc'] = trk['disc'].rjust(zpad_disc, '0')
|
438
|
+
trk['num'] = trk['num'].rjust(zpad_num, '0')
|
439
|
+
end
|
440
|
+
|
396
441
|
# Loop over the music files and
|
397
442
|
# 1. Copy them over with proper names
|
398
443
|
# 2. Fix the tags on the new files
|
@@ -402,11 +447,12 @@ fl.each do |ofname|
|
|
402
447
|
trk = tl[tn - 1]
|
403
448
|
# Use track artist for compilations, fallback to release
|
404
449
|
a = trk['artist'] || rel['artist']
|
405
|
-
d, n = mkdiscnum trk['pos'].to_s
|
406
450
|
t = trk['title']
|
407
451
|
fa = mkname a
|
408
452
|
ft = mkname t
|
409
|
-
|
453
|
+
d = trk['disc']
|
454
|
+
n = trk['num']
|
455
|
+
fd = mkname d
|
410
456
|
x = File.extname(ofname).delete('.').downcase
|
411
457
|
nfname = eval cfg['track']
|
412
458
|
# Add filename to track descriptor
|
@@ -429,22 +475,44 @@ fl.each do |ofname|
|
|
429
475
|
end
|
430
476
|
|
431
477
|
# Also save the first image of the artwork
|
432
|
-
zz = '0' * (
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
478
|
+
zz = '0' * (tl.first['disc'] + tl.first['num']).length
|
479
|
+
if rel['images'] then
|
480
|
+
relimgs = []
|
481
|
+
rel['images'].each_with_index do |imgurl, idx|
|
482
|
+
pad = rel['images'].length.to_s.length
|
483
|
+
i = idx.to_s.rjust(pad, '0')
|
484
|
+
if rel['images'].length == 1
|
485
|
+
i = ''
|
486
|
+
end
|
487
|
+
# The variable i can be used in the image template
|
488
|
+
imgname = eval cfg['image']
|
489
|
+
STDERR.puts "Save image to #{imgname}"
|
490
|
+
unless fake
|
491
|
+
# Relative path or URL
|
492
|
+
if File.exists? imgurl
|
493
|
+
img = open(imgurl).read
|
494
|
+
else
|
495
|
+
img = open(imgurl, Headers).read
|
496
|
+
end
|
497
|
+
File.open(imgname, 'wb').write img
|
498
|
+
# Update to local relative path now
|
499
|
+
relimgs << (File.basename imgname)
|
500
|
+
end
|
439
501
|
end
|
502
|
+
rel['images'] = relimgs
|
440
503
|
end
|
441
504
|
# Also save the release file for future use
|
442
505
|
relfile = eval cfg['rdata']
|
443
506
|
STDERR.puts "Save rdata to #{relfile}"
|
444
507
|
unless fake
|
445
|
-
# Sort tracklist in filename order
|
508
|
+
# Sort tracklist in filename order
|
446
509
|
rel['tracklist'].sort_by! {|s| s['file']}
|
447
|
-
|
510
|
+
# Delete temporary data
|
511
|
+
rel['tracklist'].each do |s|
|
512
|
+
s.delete 'file'
|
513
|
+
s.delete 'disc'
|
514
|
+
s.delete 'num'
|
515
|
+
end
|
448
516
|
File.open(relfile, 'w') do |f|
|
449
517
|
f.puts rel.to_yaml
|
450
518
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: musicfix
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lazaros Koromilas
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-11-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: stringex
|
@@ -70,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
70
|
version: '0'
|
71
71
|
requirements: []
|
72
72
|
rubyforge_project:
|
73
|
-
rubygems_version: 2.
|
73
|
+
rubygems_version: 2.6.13
|
74
74
|
signing_key:
|
75
75
|
specification_version: 4
|
76
76
|
summary: Music file renamer and tagger that uses Discogs.com
|