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.
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