musicfix

music file renamer and tagger
git clone git://git.2f30.org/musicfix
Log | Files | Refs | README | LICENSE

commit 4a4848953ddf8e73901a8a595adc02d320946d65
Author: lostd <lostd@2f30.org>
Date:   Fri,  6 Jul 2012 15:13:35 +0000

Time to start versioning this thingy

Diffstat:
Abin/musicfix | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 185 insertions(+), 0 deletions(-)

diff --git a/bin/musicfix b/bin/musicfix @@ -0,0 +1,185 @@ +#!/usr/bin/env ruby + +# Musicfix is a music file renamer and tagger with consistency concerns. +# It has a release-centered logic and gets data from www.discogs.com. +# First configure the target music directory (mdir variable). +# Next browse the site and locate your album. Change to the album's +# directory. Ensure the files are ordered and provide the release id +# to the program. Enjoy! + +# Dependencies: +# System packages: taglib +# Ruby gems: discogs-wrapper stringex taglib-ruby + +require 'rubygems' +require 'fileutils' +require 'open-uri' +require 'discogs' +require 'stringex' +require 'taglib' + +# Configuration +mdir = '/var/music/external' +mytrack = '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{d}#{fn}-#{fa}-#{ft}.#{x}"' +myimage = '"#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{zz}-#{fba}-#{fb}_cover.jpg"' + +# Naming conventions +# The 'f' prefix means "normalized for filename" +# +# Use everywhere: +# [f]b: album name +# [f]ba: album artist +# y: release year +# my: master release year +# g: genre style +# [f]v: release format +# +# Only for mytrack: +# [f]a: track artist +# [f]t: track title +# [f]n: track number, may have letters (vinyls) +# d: disc number +# tn: track number counter +# x: file extension in lowercase +# +# Only for myimage: +# zz: zeros that match d + n width + +# Concatenate artist list using '&' +# Convert "Sound, The (2)" to "The Sound" +def mkartist al + nl = al.collect {|a| a.name} + nl.each {|n| n.gsub! /\s\(\d+\)$/, ''} + nl.each {|n| n.gsub! /(.*), The$/, 'The \1'} + nl.join ' & ' +end + +# Convert "3" to (nil, "03") +# Convert "A3" to (nil, "A3") +# Convert "2.3" to ("2", "03") +# Convert "2.03" to ("2", "03") +# Convert "CD2-3" to ("2", "03") +def mkdiscnum pos + np = pos.gsub /[-.]/, '#' + d, n = np.split '#' + if not n + n = d + d = nil + else + d = d.gsub /\D/, '' + end + n = n.rjust(2, '0') + return d, n +end + +# Convert to lowercase ASCII without punctuation +def mkname n + n = n.gsub '12"', '12 inch' + n = n.gsub "12''", "12 inch" + n = n.gsub "12'", "12 inch" + n = n.gsub '7"', '7 inch' + n = n.gsub "7''", "7 inch" + n = n.gsub "7'", "7 inch" + n.to_url.gsub '-', '_' +end + +# Get cover artwork +def getimgurl rel + img = rel.images.select {|i| i.type == 'primary'}.first || + rel.images.first || + (return nil) + img.uri.gsub 'api.discogs.com', 's.dsimg.com' +end + +# Parse command line +relid = ARGV[0] || (puts "Usage: musicfix release_id"; exit) + +# Construct file list +fl = Dir['*'].select {|f| File.extname(f).match /mp3|ogg|mpc|flac|wv/i}.sort +puts 'Files to process:' +puts fl + +# Some test releases: +# Sound, The (2) – From The Lions Mouth: 377432 +# Various – Return Of The Banshee: 127565 +# Harold Budd & Brian Eno – The Pearl: 699395 +# Dead Souls Rising – Clepsydre 1993-1999: 1212954 +# Metro Decay – Υπέρβαση: 1766242 +# Dispossessed, The (2) – Sister Mary: 1965566 + +# Get release data +puts "Geting release data..." +discogs = Discogs::Wrapper.new("musicfix") +r = discogs.get_release(relid) +mr = if r.master_id then discogs.get_master_release(r.master_id) end +# Tracklist can contain dummy header tracks, strip them +tl = r.tracklist.select {|t| t.position} + +# Sanity checks +if tl.length != fl.length then + puts "Found #{tl.length} tracks for #{fl.length} music files." + exit if fl.length > tl.length + exit if fl.length == 0 + puts "Use only the first #{fl.length} entries? [y/n] " + res = STDIN.readline.strip + exit unless res == 'y' +end + +# Gather release-wide data +ba = mkartist r.artists +b = r.title +y = r.released +my = if mr then mr.year end || r.released +# Year can be full-date so keep only the year part +y = y.slice(0..3) +my = my.slice(0..3) +g = if r.styles then r.styles.first end || + if r.genres then r.genres.first end +fba = mkname ba +fb = mkname b +v = r.formats.first.name +fv = mkname v + +# Loop over the music files and +# 1. Copy them over with proper names +# 2. Fix the tags on the new files +tn = 0 +fl.each do |ofname| + tn = tn.next + s = tl[tn - 1] + al = (r.artists.first.name == "Various") ? s.artists : r.artists + a = mkartist al + d, n = mkdiscnum s.position + t = s.title + fa = mkname a + ft = mkname t + fn = mkname n + x = File.extname(ofname).delete('.').downcase + #nfname = "#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{d}#{fn}-#{fa}-#{ft}.#{x}" + nfname = eval mytrack + puts "Copy track to #{nfname}" + FileUtils.makedirs(File.dirname nfname) + FileUtils.copy(ofname, nfname) + TagLib::FileRef.open(nfname) do |f| + f.tag.artist = a + f.tag.album = b + f.tag.title = t + f.tag.track = tn + f.tag.year = y.to_i + f.tag.genre = g + f.save + end +end + +# Also save the first image of the artwork +zz = '0' * (mkdiscnum tl.last.position).join.length +#imgname = "#{mdir}/#{fba}-#{my}-#{fb}-#{fv}/#{zz}-#{fba}-#{fb}_cover.jpg" +imgname = eval myimage +imgurl = getimgurl r +if imgurl then + puts "Save cover to #{imgname}" + img = open(imgurl, {"User-Agent" => "Mozilla"}).read + File.open(imgname, 'wb').write img +end + +# vim:set ts=4 sw=4 et: