Skip to content

Instantly share code, notes, and snippets.

@kreeger
Last active June 9, 2016 13:45
Show Gist options
  • Save kreeger/9511399 to your computer and use it in GitHub Desktop.
Save kreeger/9511399 to your computer and use it in GitHub Desktop.
Converts a directory full of MP3s and M4As into uniform, -V5 MP3 files, filed away into directories, tagged with ID3 tags, ready to burn onto an MP3 CD.
#!/usr/bin/env ruby
require 'fileutils'
require 'pp'
directory = File.expand_path ARGV[0]
args = ARGV[1,ARGV.length-1].join(' ')
class Track
attr_accessor :source, :destination, :base_dir
attr_accessor :artist, :album, :song, :track_number, :year
def initialize(source, base_dir)
self.source = source
self.base_dir = base_dir
self.destination = File.basename(source).gsub(/\.\w+$/, '.mp3')
if File.extname(self.source) == '.mp3'
self.read_tags_from_mp3
else
self.read_tags_from_mp4
end
end
def directory_name
[self.artist, self.album].join(' - ')
end
def read_tags_from_mp3
id3 = `id3v2 -l \"#{self.source}\"`.unpack('C*').pack('U*')
self.song = find_matches(id3, /^TT2 .+: (.*)$/, /^TIT2 .+: (.*)$/)
self.artist = find_matches(id3, /^TP1 .+: (.*)$/, /^TPE1 .+: (.*)$/, /^TPE2 .+: (.*)$/)
self.album = find_matches(id3, /^TAL .+: (.*)$/, /^TALB .+: (.*)$/)
self.year = find_matches(id3, /^TYE .+: (.*)$/, /^TYER .+: (.*)$/)
self.track_number = find_matches(id3, /^TRK .+: (.*)$/, /^TRCK .+: (.*)$/).split("/").first
end
def read_tags_from_mp4
parsley = `atomicparsley \"#{self.source}\" -t`
self.song = find_matches(parsley, /©nam" contains: (.*)/)
self.artist = find_matches(parsley, /©ART" contains: (.*)/)
self.album = find_matches(parsley, /©alb" contains: (.*)/)
self.track_number = find_matches(parsley, /trkn" contains: (.*)/).strip.split(' of ').join('/')
self.year = find_matches(parsley, /©day" contains: (.*)/)
end
def find_matches(data, *patterns)
match = nil
while match == nil do
break if patterns.count == 0
pattern = patterns.pop
match = pattern && pattern.match(data)
end
match && match[1] && match[1].strip
end
def full_destination
File.join(self.base_dir, self.directory_name, self.destination)
end
def wave_path
self.full_destination.gsub(/\.\w+$/, '.wav')
end
def convert!
FileUtils.mkdir_p(File.join(self.base_dir, self.directory_name))
result = self.to_wave
if result == 0
result2 = self.to_mp3
if !result2
puts "ERROR! Tried to encode #{File.basename(self.wave_path)} to MP3 and failed with exit status #{result2}."
else
self.write_tags!
end
else
puts "ERROR! Tried to encode #{File.basename(self.source)} to WAV and failed with exit status #{result}."
end
self.cleanup
end
def write_tags!
`id3v2 -a \"#{self.artist}\" -A \"#{self.album}\" -t \"#{self.song}\" -y #{self.year} -T #{self.track_number} \"#{self.full_destination}\"`
end
def to_wave
`afconvert -f 'WAVE' -d I16@44100 "#{self.source}" "#{self.wave_path}"`
$?.exitstatus
end
def to_mp3
`lame -V5 \"#{self.wave_path}\" \"#{self.full_destination}\"`
$?.exitstatus
end
def cleanup
File.delete(self.wave_path) rescue nil
end
end
def traverse(base_dir, current_dir, env)
Dir.entries(current_dir).each do |file|
next if %r(^\.).match(file)
basename = File.basename(file)
file = File.join(current_dir, file)
if File.directory?(file)
traverse(base_dir, file, env)
next
end
env[:tracks] << Track.new(file, base_dir)
end
end
env = {tracks: []}
traverse(directory, directory, env)
env[:tracks].each do |track|
track.convert!
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment