musicfix 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +5 -0
  3. data/README +17 -14
  4. data/bin/musicfix +137 -69
  5. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 12ba6b8750f8ba21d9d439843de08a22a590ff65
4
- data.tar.gz: ef0cf64ea385518284eab1dfc85a0d77f43b5367
3
+ metadata.gz: 5b01d3bf3f2c2fc81008328a152fdf80a191041d
4
+ data.tar.gz: 66e75cc847b3e4d355ef3d37d8e76748b1a597ca
5
5
  SHA512:
6
- metadata.gz: 86728467e24e4099e13b080d8f3127151939c53bcf234d05e334c195f69f4849c86c4b4c676d99ff72088330acea8e7946575c1f051fd6db3c9fe49938441676
7
- data.tar.gz: 3d17efef7167e023755bb0d1a87a4545eaa01602bee11ad59531382a11619c5ac2e69dd739d915047a7f029d3a3e5dd129ddee79b8293e6df57878d56a1605cb
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}/#{d}#{fn}-#{fa}-#{ft}.#{x}"'
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]n: track number, may have letters (vinyls)
97
- d: disc number
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
- # Some test releases
106
+ i: the index in the list
107
107
 
108
- * Sound, The (2) – From The Lions Mouth: 377432
109
- * Various Return Of The Banshee: 127565
110
- * Harold Budd & Brian Eno – The Pearl: 699395
111
- * Dead Souls Rising Clepsydre 1993-1999: 1212954
112
- * Metro Decay Υπέρβαση: 1766242
113
- * Dispossessed, The (2) Sister Mary: 1965566
114
- * Einstürzende Neubauten Strategies Against Architecture IV: 2506503
115
- * David Bowie – The Man Who Sold The World: 484622
116
- * Various – Some Bizzare Album: 1381202
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.
@@ -9,7 +9,7 @@ require 'taglib'
9
9
  require 'yaml'
10
10
 
11
11
  # Headers
12
- Ver = '0.1.7'
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 (nil, "A1")
29
- # Convert "A3" to (nil, "A3")
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
- if n.match /[A-Z]/
43
- n = n.ljust(2, '1')
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 getimgurl rel
82
+ def getimages rel
74
83
  return nil unless rel['images']
75
- img = rel['images'].select {|i| i['type'] == 'primary'}.first ||
76
- rel['images'].first
77
- img['uri'].gsub! 'api.discogs.com/images/R-90', 's.pixogs.com/image/R'
78
- img['uri'].gsub! 'api.discogs.com/images/', 's.pixogs.com/image/'
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
- # Ignore in favor of LP/EP/7"/10"/12" descriptions
105
- unless format['name'] == "Vinyl"
106
- f << format['name']
156
+ formats = []
157
+ if format['name'] then
158
+ formats << format['name']
107
159
  end
108
- if format['descriptions']
109
- # Too general
110
- format['descriptions'].delete "Album"
111
- unless format['descriptions'].empty?
112
- f << format['descriptions'].first
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}/#{d}#{fn}-#{fa}-#{ft}.#{x}"'
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['imgurl'] = nil
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("http://api.discogs.com/releases/#{relid}",
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("http://api.discogs.com/masters/#{r['master_id']}",
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
- rel['imgurl'] = getimgurl r
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
- fn = mkname n
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' * (mkdiscnum tl.last['pos'].to_s).join.length
433
- imgname = eval cfg['image']
434
- if rel['imgurl'] then
435
- STDERR.puts "Save image to #{imgname}"
436
- unless fake
437
- img = open(rel['imgurl'], Headers).read
438
- File.open(imgname, 'wb').write img
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 and delete filenames
508
+ # Sort tracklist in filename order
446
509
  rel['tracklist'].sort_by! {|s| s['file']}
447
- rel['tracklist'].each {|s| s.delete 'file'}
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.1.7
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: 2014-10-01 00:00:00.000000000 Z
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.2.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