summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xomptagger708
1 files changed, 318 insertions, 390 deletions
diff --git a/omptagger b/omptagger
index b489e13..6bf28b0 100755
--- a/omptagger
+++ b/omptagger
@@ -1,6 +1,6 @@
#!/usr/bin/env ruby
#
-# omptagger [version 0.8.1]
+# omptagger [version 1.0]
# http://dev.gentoo.org/~omp/omptagger/
#
# Copyright 2007 David Shakaryan <omp@gentoo.org>
@@ -12,113 +12,132 @@
# on the format of the file being worked with.
#
# Dependencies:
-# * ruby [ http://www.ruby-lang.org/ ]
-# * flac [ http://flac.sourceforge.net/ ]
-# * id3lib-ruby [ http://id3lib-ruby.rubyforge.org/ ]
-# * vorbis-tools [ http://www.vorbis.com/ ]
+# * ruby http://www.ruby-lang.org/
+# * flac http://flac.sourceforge.net/
+# * id3lib-ruby http://id3lib-ruby.rubyforge.org/
+# * vorbis-tools http://www.vorbis.com/
require 'getoptlong'
require 'id3lib'
-# List of valid options.
-getopt = GetoptLong.new(
- ['--view', '-v', GetoptLong::NO_ARGUMENT],
- ['--view-tag', '-t', GetoptLong::REQUIRED_ARGUMENT],
- ['--set-tag', '-s', GetoptLong::REQUIRED_ARGUMENT],
- ['--generate', '-g', GetoptLong::NO_ARGUMENT],
- ['--preview', '-p', GetoptLong::NO_ARGUMENT],
- ['--remove', '-r', GetoptLong::NO_ARGUMENT],
- ['--remove-tag', '-d', GetoptLong::REQUIRED_ARGUMENT],
- ['--rename', '-m', GetoptLong::NO_ARGUMENT],
- ['--scheme', '-n', GetoptLong::REQUIRED_ARGUMENT],
- ['--no-colour', '-c', GetoptLong::NO_ARGUMENT],
- ['--help', '-h', GetoptLong::NO_ARGUMENT]
-)
-
-# Store any options set in a hash. Create a tracker variable to determine
-# whether an action has been set.
-action = false
-$opts = Hash['scheme' => '%a - %t']
-getopt.each do |opt, arg|
- opt.sub!('--', '')
-
- case opt
- # Options which may be used only once.
- when 'view', 'generate', 'preview', 'remove', 'rename', 'help'
- $opts[opt] = arg
- action = true
- when 'no-colour', 'scheme'
- $opts[opt] = arg
-
- # Options which may be used more than once.
- when 'view-tag', 'set-tag', 'remove-tag'
- if $opts.has_key?(opt)
- $opts[opt].push(arg)
- else
- $opts[opt] = [arg]
- end
- action = true
- end
-end
-
-# Define colours unless the no-colour option is set.
-if $opts.has_key?('no-colour')
- def colred(str); return str; end
- def colgrn(str); return str; end
- def colyel(str); return str; end
- def colcyn(str); return str; end
-else
- def colred(str); return "\e[31m#{str}\e[0m"; end
- def colgrn(str); return "\e[32m#{str}\e[0m"; end
- def colyel(str); return "\e[33m#{str}\e[0m"; end
- def colcyn(str); return "\e[36m#{str}\e[0m"; end
-end
+# Define colours.
+def colred(str) return "\e[31m#{str}\e[0m" end
+def colgrn(str) return "\e[32m#{str}\e[0m" end
+def colyel(str) return "\e[33m#{str}\e[0m" end
+def colcyn(str) return "\e[36m#{str}\e[0m" end
-# Methods for outputting warning and status messages.
-def warn(message)
- puts ' ' + colred('ERROR:') + ' ' + message
-end
-def status(message)
- puts ' ' + colgrn(message)
-end
-
-# Method for escaping single quotes for use within single quotes in shell
-# commands. Substitutes all instances of ' with '\''.
+# Escape single quotes for use within single quotes in shell commands.
def esc(str)
+ # Substitute all instances of ' with '\''.
return str.gsub("'", "'\\\\''")
end
-# Method for dynamic spacing; used to correctly align tag and value output.
-def spacing(tags, tag)
- return ' ' * (tags.map do |str| str.size end.max + 2 - tag.length)
+# Output a tag name and value.
+def output(tag, val)
+ # Alter capitalisation of tag name for aesthetic purposes.
+ tag = tag.downcase.capitalize unless tag == 'ISRC'
+ tag = 'TrackNumber' if tag == 'Tracknumber'
+
+ # The number statically defined here should be two greater than the length of
+ # the longest possible tag name.
+ puts ' ' + colcyn(tag) + ' ' * (14 - tag.length) + val
end
-# Method for splitting filename based on a naming scheme.
-def namingscheme(scheme, name)
- regexp = /\A#{Regexp.escape(scheme).gsub(/%[a-z]/, '(.+?)')}\Z/
- tags = scheme.scan(regexp).flatten
- values = name.scan(regexp).flatten
+# Split a filename based on a naming scheme. An asterisk is a wildcard key.
+def schemesplit(scheme, file, keystr)
+ # Determine keys and values using a regular expression.
+ regexp = /\A#{Regexp.escape(scheme).gsub(/%([#{keystr}]|\\\*)/, '(.*?)')}\Z/
+ keys = scheme.scan(regexp).flatten
+ vals = file.scan(regexp).flatten
- unless tags.length == values.length
- raise 'Filename does not match naming scheme.'
- end
+ raise 'Filename does not match naming scheme.' if keys.length != vals.length
+ # Map scheme keys to values.
result = Hash.new
- tags.zip(values).each do |tag, value|
- result[tag.delete('%')] = value
+ keys.zip(vals).each do |key, val|
+ result[key[1,1]] = val unless key == '%*'
end
return result
end
+# Display help information.
+def help
+ options = [['--view -v', 'View all tags'],
+ ['--view-tag -t', 'View a tag'],
+ ['--set-tag -s', 'Set a tag'],
+ ['--remove -r', 'Remove all tags'],
+ ['--remove-tag -d', 'Remove a tag'],
+ ['--generate -g', 'Generate tags based on filename'],
+ ['--rename -m', 'Generate filename based on tags'],
+ ['--scheme -n', 'Specify a file naming scheme'],
+ ['--pretend -p', 'Do not make any actual changes'],
+ ['--no-colour -c', 'Disable use of colour in output'],
+ ['--list -l', 'Display list of available tags'],
+ ['--help -h', 'Display help information'],
+ ['--rename -m', 'Generate filename based on tags']]
+ notes = ['The default file naming scheme is Artist - Title.',
+ 'Schemes must be specified prior to actions that use them.',
+ 'Underscores in filenames are converted to spaces in tags.']
+
+ puts colyel('Usage:') + ' omptagger ' + colgrn('[options] [files]')
+ puts
+ puts colyel('Options:')
+ options.each do |option, description|
+ puts ' ' + colgrn(option) + ' ' + description
+ end
+ puts
+ puts colyel('Notes:')
+ notes.each do |note|
+ puts ' ' + colgrn('*') + ' ' + note
+ end
+ exit
+end
+
+# Display list of available tags.
+def list
+ puts '┌───────────────────────────────────┐',
+ '│ Keys Vorbis Comments ID3 Tags │',
+ '├───────────────────────────────────┤',
+ '│ %a Artist Artist │',
+ '│ %b Album Album │',
+ '│ %d Date Year │',
+ '│ %n TrackNumber Track │',
+ '│ %t Title Title │',
+ '│ %* Wildcard Wildcard │',
+ '├───────────────────────────────────┤',
+ '│ Contact │',
+ '│ Copyright │',
+ '│ Description │',
+ '│ Genre │',
+ '│ ISRC │',
+ '│ License │',
+ '│ Location │',
+ '│ Organization │',
+ '│ Performer │',
+ '│ Version │',
+ '└───────────────────────────────────┘'
+ exit
+end
+
# Class for Vorbis comments; used by FLAC and Vorbis files.
class VorbisComments
- attr_reader :hash, :file
-
# Possible tags.
- TAGS = ['TITLE', 'VERSION', 'ALBUM', 'TRACKNUMBER', 'ARTIST', 'PERFORMER',
- 'COPYRIGHT', 'LICENSE', 'ORGANIZATION', 'DESCRIPTION', 'GENRE',
- 'DATE', 'LOCATION', 'CONTACT', 'ISRC']
+ TAGS = ['ALBUM',
+ 'ARTIST',
+ 'CONTACT',
+ 'COPYRIGHT',
+ 'DATE',
+ 'DESCRIPTION',
+ 'GENRE',
+ 'ISRC',
+ 'LICENSE',
+ 'LOCATION',
+ 'ORGANIZATION',
+ 'PERFORMER',
+ 'TITLE',
+ 'TRACKNUMBER',
+ 'VERSION']
# Map scheme keys to tags.
KEYS = Hash['a' => 'ARTIST',
@@ -127,206 +146,160 @@ class VorbisComments
'n' => 'TRACKNUMBER',
't' => 'TITLE']
- # Method for outputting a tag and its corresponding value.
- def output(tag, value)
- puts ' ' + colcyn(tag) + spacing(TAGS, tag) + value
- end
-
- # Method for initialising a new object.
def initialize(file)
- @file = file
+ @file = @origfile = file
@tags = Hash.new
+ @write = false
TAGS.each do |tag|
- value = read_tag(tag)
- next if value.empty?
-
- @tags[tag] = value.split("\n").map do |str| str.sub(tag + '=', '') end
- end
- end
-
- # Method for writing tags.
- def write_tags
- clear_tags
+ val = read_tag(tag)
+ next if val.empty?
- @tags.each do |tag, value|
- value.each do |value|
- write_tag(tag, value)
- end
+ @tags[tag] = val.gsub(/^#{tag}=/i, '').split("\n")
end
end
- # Method for displaying all tags.
def view
raise 'No tags are set.' if @tags.empty?
- @tags.each do |tag, value|
- value.each do |value|
- output(tag, value)
+ @tags.each do |tag, val|
+ val.each do |val|
+ output(tag, val)
end
end
end
- # Method for displaying certain tags.
def view_tag(arg)
- arg.each do |arg|
- # Process exceptions within the argument loop, as using the exception in
- # the file loop may end the argument loop prematurely.
- begin
- tag = arg.upcase
- raise tag + ' is not a valid tag.' unless TAGS.include?(tag)
+ tag = arg.upcase
+ raise tag + ' is not a valid tag.' unless TAGS.include?(tag)
- value = @tags[tag]
- raise tag + ' tag is not set.' if value.nil?
+ val = @tags[tag]
+ raise tag + ' tag is not set.' if val.nil?
- value.each do |value|
- output(tag, value)
- end
- rescue RuntimeError => message
- warn(message)
- end
+ val.each do |val|
+ output(tag, val)
end
end
- # Method for setting tags.
def set_tag(arg)
- status('Setting tags...')
+ arg = arg.split('=', 2)
+ tag = arg.shift.upcase
+ raise tag + ' is not a valid tag.' unless TAGS.include?(tag)
- arg.each do |arg|
- # Process exceptions within the argument loop, as using the exception in
- # the file loop may end the argument loop prematurely.
- begin
- arg = arg.split('=', 2)
- tag = arg.first.upcase
- raise tag + ' is not a valid tag.' unless TAGS.include?(tag)
-
- if arg.length == 1
- value = ''
- else
- value = arg.last
- end
+ @tags[tag] = arg
+ view_tag(tag)
- @tags[tag] = [value]
- view_tag(tag)
- rescue RuntimeError => message
- warn(message)
- end
- end
+ @write = true
end
- # Method for generating new tags based on filename.
- def generate
- value = File.basename(@file).gsub('_', ' ').sub(/\.(flac|ogg)$/, '')
- scheme = namingscheme($opts['scheme'].gsub('_', ' '), value)
-
- tags = Array.new
- values = Array.new
-
- scheme.each do |tag, value|
- raise 'Naming scheme contains an invalid key.' unless KEYS.has_key?(tag)
-
- tags.push(KEYS[tag])
- values.push(value)
- end
-
- status('Generating tags...')
-
- tags.zip(values).each do |arr|
- @tags[arr.first] = [arr.last]
- view_tag(arr.first)
- end
- end
-
- # Method for removing all tags.
def remove
raise 'No tags are set.' if @tags.empty?
- status('Removing tags...')
@tags.clear
+ @write = true
end
- # Method for removing certain tags.
def remove_tag(arg)
- status('Removing tags...')
+ tag = arg.upcase
+ raise tag + ' is not a valid tag.' unless TAGS.include?(tag)
+ raise tag + ' tag is not set.' unless @tags.include?(tag)
- arg.each do |arg|
- # Process exceptions within the argument loop, as using the exception in
- # the file loop may end the argument loop prematurely.
- begin
- tag = arg.upcase
- raise tag + ' is not a valid tag.' unless TAGS.include?(tag)
- raise tag + ' tag is not set.' unless @tags.include?(tag)
+ @tags.delete(tag)
+ @write = true
+ end
- @tags.delete(tag)
- rescue RuntimeError => message
- warn(message)
- end
+ def generate(scheme)
+ val = File.basename(@file, File.extname(@file)).gsub('_', ' ')
+ scheme = schemesplit(scheme.gsub('_', ' '), val, KEYS.keys.join)
+
+ tagval = Array.new
+
+ scheme.each do |tag, val|
+ tagval << [KEYS[tag], val]
end
+
+ tagval.each do |arr|
+ @tags[arr.first] = [arr.last]
+ view_tag(arr.first)
+ end
+
+ @write = true
end
- # Method for renaming files based on tags.
- def rename
- file = $opts['scheme']
+ def rename(scheme)
+ file = scheme
- file.scan(/%([a-z])/).flatten.each do |key|
- raise 'Naming scheme contains an invalid key.' unless KEYS.has_key?(key)
+ file.scan(/%([#{KEYS.keys.join}])/).flatten.each do |key|
raise 'Missing ' + KEYS[key] + ' tag.' unless @tags[KEYS[key]]
-
file = file.gsub('%' + key, @tags[KEYS[key]].first)
end
file = File.dirname(@file) + '/' + file.gsub('/', '_') +
- @file.sub(/.*\./, '.')
+ File.extname(@file)
+
+ raise 'Generated filename and current filename are identical.' if
+ File.basename(@file) == File.basename(file)
+
+ output('FILENAME', file.sub(/^\.\//, ''))
+ raise 'File with generated name already exists.' if File.exist?(file)
+
+ @file = file
+ end
+
+ def finalise
+ if @write
+ clear_tags
- raise 'Generated filename and current filename are identical.' \
- if File.basename(@file) == File.basename(file)
+ @tags.each do |tag, val|
+ val.each do |val|
+ write_tag(tag, val)
+ end
+ end
+ end
- status('Renaming to ' + file.sub(/^\.\//, '') + '...')
- File.rename(@file, file)
+ File.rename(@origfile, @file) unless @file == @origfile
end
end
# Subclass of VorbisComments containing methods unique to FLAC files.
class FLAC < VorbisComments
def read_tag(tag)
- %x(metaflac --show-tag=#{tag} -- '#{esc(@file)}').chomp
+ %x(metaflac --show-tag=#{tag} -- '#{esc(@origfile)}').chomp
end
def clear_tags
- %x{metaflac --remove-all-tags -- '#{esc(@file)}'}
+ %x{metaflac --remove-all-tags -- '#{esc(@origfile)}'}
end
- def write_tag(tag, value)
- %x{metaflac --set-tag=#{tag}='#{esc(value)}' -- '#{esc(@file)}'}
+ def write_tag(tag, val)
+ %x{metaflac --set-tag=#{tag}='#{esc(val)}' -- '#{esc(@origfile)}'}
end
end
# Subclass of VorbisComments containing methods unique to Vorbis files.
class Vorbis < VorbisComments
def read_tag(tag)
- %x(vorbiscomment -l -- '#{esc(@file)}' | grep '#{tag}').chomp
+ %x(vorbiscomment -l -- '#{esc(@origfile)}' | grep '^#{tag}=').chomp
end
def clear_tags
- %x{vorbiscomment -w -t '' -- '#{esc(@file)}' 2>/dev/null}
+ %x{vorbiscomment -w -t '' -- '#{esc(@origfile)}' 2>/dev/null}
end
- def write_tag(tag, value)
- %x{vorbiscomment -a -t #{tag}='#{esc(value)}' -- '#{esc(@file)}'}
+ def write_tag(tag, val)
+ %x{vorbiscomment -a -t #{tag}='#{esc(val)}' -- '#{esc(@origfile)}'}
end
end
# Class for ID3 tags; used by MP3 files.
class ID3
- attr_reader :file, :tags
-
# Possible tags.
- TAGS = Hash['TITLE' => :TIT2,
- 'ALBUM' => :TALB,
- 'TRACK' => :TRCK,
+ TAGS = Hash['ALBUM' => :TALB,
'ARTIST' => :TPE1,
- 'YEAR' => :TYER,
- 'COMMENT' => :COMM]
+ 'COMMENT' => :COMM,
+ 'TITLE' => :TIT2,
+ 'TRACK' => :TRCK,
+ 'YEAR' => :TYER]
# Map scheme keys to tags.
KEYS = Hash['a' => 'ARTIST',
@@ -335,252 +308,207 @@ class ID3
'n' => 'TRACK',
't' => 'TITLE']
- # Method for outputting a tag and its corresponding value.
- def output(tag, value)
- puts ' ' + colcyn(tag) + spacing(TAGS.keys, tag) + value
- end
-
- # Method for initialising a new object.
def initialize(file)
- @file = file
+ @file = @origfile = file
@tags = ID3Lib::Tag.new(@file)
+ @write = false
end
- # Method for writing tags.
- def write_tags
- if @tags.empty? or (@tags.length == 1 and @tags.frame_text(:TLEN))
- @tags.strip!
- else
- @tags.update!
- end
- end
-
- # Method for displaying all tags.
def view
raise 'No tags are set.' if @tags.empty?
TAGS.each do |tag, frame|
- value = @tags.frame_text(frame)
- output(tag, value) unless value.nil?
+ val = @tags.frame_text(frame)
+ output(tag, val) unless val.nil?
end
end
- # Method for displaying certain tags.
def view_tag(arg)
- arg.each do |arg|
- # Process exceptions within the argument loop, as using the exception in
- # the file loop may end the argument loop prematurely.
- begin
- tag = arg.upcase
- raise tag + ' is not a valid tag.' unless TAGS.has_key?(tag)
+ tag = arg.upcase
+ raise tag + ' is not a valid tag.' unless TAGS.has_key?(tag)
- value = @tags.frame_text(TAGS[tag])
- raise tag + ' tag is not set.' if value.nil?
+ val = @tags.frame_text(TAGS[tag])
+ raise tag + ' tag is not set.' if val.nil?
- output(tag, value)
- rescue RuntimeError => message
- warn(message)
- end
- end
+ output(tag, val)
end
- # Method for setting tags.
def set_tag(arg)
- status('Setting tags...')
+ arg = arg.split('=', 2)
+ tag = arg.shift.upcase
+ raise tag + ' is not a valid tag.' unless TAGS.has_key?(tag)
- arg.each do |arg|
- # Process exceptions within the argument loop, as using the exception in
- # the file loop may end the argument loop prematurely.
- begin
- arg = arg.split('=', 2)
- tag = arg.first.upcase
- raise tag + ' is not a valid tag.' unless TAGS.has_key?(tag)
-
- if arg.length == 1
- value = ''
- else
- value = arg.last
- end
+ @tags.set_frame_text(TAGS[tag], arg.to_s)
+ view_tag(tag)
- @tags.set_frame_text(TAGS[tag], value)
- view_tag(tag)
- rescue RuntimeError => message
- warn(message)
- end
- end
+ @write = true
end
- # Method for generating new tags based on filename.
- def generate
- value = File.basename(@file).gsub('_', ' ').sub(/\.mp3$/, '')
- scheme = namingscheme($opts['scheme'].gsub('_', ' '), value)
-
- tags = Array.new
- values = Array.new
+ def generate(scheme)
+ val = File.basename(@file, File.extname(@file)).gsub('_', ' ')
+ scheme = schemesplit(scheme.gsub('_', ' '), val, KEYS.keys.join)
- scheme.each do |tag, value|
- raise 'Naming scheme contains an invalid key.' unless KEYS.has_key?(tag)
+ tagval = Array.new
- tags.push(KEYS[tag])
- values.push(value)
+ scheme.each do |tag, val|
+ tagval << [KEYS[tag], val]
end
- status('Generating tags...')
-
- tags.zip(values).each do |arr|
+ tagval.each do |arr|
@tags.set_frame_text(TAGS[arr.first], arr.last)
view_tag(arr.first)
end
+
+ @write = true
end
- # Method for removing all tags.
def remove
raise 'No tags are set.' if @tags.empty?
- status('Removing tags...')
@tags.clear
+ @write = true
end
- # Method for removing certain tags.
def remove_tag(arg)
- status('Removing tags...')
+ tag = arg.upcase
+ raise tag + ' is not a valid tag.' unless TAGS.has_key?(tag)
+ raise tag + ' tag is not set.' if @tags.frame_text(TAGS[tag]).nil?
- arg.each do |arg|
- # Process exceptions within the argument loop, as using the exception in
- # the file loop may end the argument loop prematurely.
- begin
- tag = arg.upcase
- raise tag + ' is not a valid tag.' unless TAGS.has_key?(tag)
- raise tag + ' tag is not set.' if @tags.frame_text(TAGS[tag]).nil?
-
- @tags.remove_frame(TAGS[tag])
- rescue RuntimeError => message
- warn(message)
- end
- end
+ @tags.remove_frame(TAGS[tag])
+ @write = true
end
- # Method for renaming files based on tags.
- def rename
- file = $opts['scheme']
+ def rename(scheme)
+ file = scheme
- file.scan(/%([a-z])/).flatten.each do |key|
- raise 'Naming scheme contains an invalid key.' unless KEYS.has_key?(key)
- raise 'Missing ' + KEYS[key] + ' tag.' \
- if @tags.frame_text(TAGS[KEYS[key]]).nil?
+ file.scan(/%([#{KEYS.keys.join}])/).flatten.each do |key|
+ raise 'Missing ' + KEYS[key] + ' tag.' if
+ @tags.frame_text(TAGS[KEYS[key]]).nil?
file = file.gsub('%' + key, @tags.frame_text(TAGS[KEYS[key]]))
end
file = File.dirname(@file) + '/' + file.gsub('/', '_') +
- @file.sub(/.*\./, '.')
+ File.extname(@file)
- raise 'Generated filename and current filename are identical.' \
- if File.basename(@file) == File.basename(file)
+ raise 'Generated filename and current filename are identical.' if
+ File.basename(@file) == File.basename(file)
- status('Renaming to ' + file.sub(/^\.\//, '') + '...')
- File.rename(@file, file)
+ output('FILENAME', file.sub(/^\.\//, ''))
+ raise 'File with generated name already exists.' if File.exist?(file)
+
+ @file = file
+ end
+
+ def finalise
+ if @write
+ if @tags.empty? or (@tags.length == 1 and @tags.frame_text(:TLEN))
+ @tags.strip!
+ else
+ @tags.update!
+ end
+ end
+
+ File.rename(@origfile, @file) unless @file == @origfile
end
end
-# Display program help if help action is set. If no actions are set, default to
-# either help or view, depending on whether an argument was passed.
-if $opts.has_key?('help') or (!action and ARGV.empty?)
- puts colyel('Usage:') + ' omptagger ' + colgrn('[options]') + ' ' +
- colgrn('[files]')
- puts
- puts colyel('Options:')
- puts ' ' + colgrn('--view') + ' ' + colgrn('-v') +
- ' Display all tags'
- puts ' ' + colgrn('--view-tag') + ' ' + colgrn('-t') +
- ' Display a tag ' + colcyn('[required argument: tag]')
- puts ' ' + colgrn('--set-tag') + ' ' + colgrn('-s') +
- ' Set a tag ' + colcyn('[required argument: tag=string]')
- puts ' ' + colgrn('--generate') + ' ' + colgrn('-g') +
- ' Generate tags based on filename'
- puts ' ' + colgrn('--preview') + ' ' + colgrn('-p') +
- ' Preview tags that will be set with generate option'
- puts ' ' + colgrn('--remove') + ' ' + colgrn('-r') +
- ' Remove all tags'
- puts ' ' + colgrn('--remove-tag') + ' ' + colgrn('-d') +
- ' Remove a tag ' + colcyn('[required argument: tag]')
- puts ' ' + colgrn('--rename') + ' ' + colgrn('-m') +
- ' Rename files based on tags'
- puts ' ' + colgrn('--scheme') + ' ' + colgrn('-n') +
- ' Allow a file naming scheme to be specified'
- puts ' ' + colgrn('--no-colour') + ' ' + colgrn('-c') +
- ' Disable use of colour in program output'
- puts ' ' + colgrn('--help') + ' ' + colgrn('-h') +
- ' Display program help'
- puts
- puts colyel('Notes:')
- puts ' ' + colgrn('*') + ' Default file naming scheme is ' +
- colcyn('Artist - Title') + '.'
- puts ' ' + colgrn('*') + ' Underscores in filenames are converted to ' +
- 'spaces in tags.'
- puts ' ' + colgrn('*') + ' Scheme keys: ' + colcyn('%a') + ' - ARTIST, ' +
- colcyn('%b') + ' - ALBUM, ' + colcyn('%d') + ' - DATE/YEAR,'
- puts ' ' + ' ' + colcyn('%n') + ' - TRACKNUMBER/TRACK, ' +
- colcyn('%t') + ' - TITLE.'
- exit 0
-elsif !action
- $opts['view'] = ''
+# Parse options.
+actions = Array.new
+options = Array.new
+scheme = '%a - %t'
+GetoptLong.new(
+ ['--view', '-v', GetoptLong::NO_ARGUMENT],
+ ['--view-tag', '-t', GetoptLong::REQUIRED_ARGUMENT],
+ ['--set-tag', '-s', GetoptLong::REQUIRED_ARGUMENT],
+ ['--remove', '-r', GetoptLong::NO_ARGUMENT],
+ ['--remove-tag', '-d', GetoptLong::REQUIRED_ARGUMENT],
+ ['--generate', '-g', GetoptLong::NO_ARGUMENT],
+ ['--rename', '-m', GetoptLong::NO_ARGUMENT],
+ ['--scheme', '-n', GetoptLong::REQUIRED_ARGUMENT],
+ ['--pretend', '-p', GetoptLong::NO_ARGUMENT],
+ ['--no-colour', '-c', GetoptLong::NO_ARGUMENT],
+ ['--list', '-l', GetoptLong::NO_ARGUMENT],
+ ['--help', '-h', GetoptLong::NO_ARGUMENT]
+).each do |opt, arg|
+ opt = opt.sub(/^--/, '').gsub('-', '_').intern
+
+ case opt
+ when :no_colour
+ def colred(str) return str end
+ def colgrn(str) return str end
+ def colyel(str) return str end
+ def colcyn(str) return str end
+ when :scheme
+ scheme = arg
+ when :pretend, :help, :list
+ options << opt
+ when :view_tag, :set_tag, :remove_tag
+ actions << [opt, arg]
+ when :generate, :rename
+ actions << [opt, scheme]
+ else
+ actions << [opt]
+ end
+end
+
+# Deal with help and list options. Set a default action if one was not
+# specified. Display error if an action was specified, but no arguments are
+# present.
+if options.include?(:help)
+ help
+elsif options.include?(:list)
+ list
+elsif actions.empty? and ARGV.empty?
+ help
+elsif actions.empty?
+ actions = [[:view]]
+elsif ARGV.empty?
+ puts colred('ERROR:') + ' No files specified.'
end
-# Treat all remaining arguments as files.
-warn('No files specified.') if ARGV.length.zero?
+# Treat each argument as a file.
ARGV.each do |file|
- # Process exceptions in order to produce error messages in a custom format
- # without an excessive number of nested if statements.
+ # Continue with next file if an error is raised.
begin
- # Output name of file.
+ # Output filename and verify that file exists.
puts colyel(file)
-
- # Verify that file exists.
raise 'File does not exist.' unless File.exist?(file)
# Determine file format.
- raise 'File has no extension.' if (file =~ /\./).nil?
- case file.sub(/.*\./, '')
- when 'flac'
+ case File.extname(file).downcase
+ when '.flac'
track = FLAC.new(file)
- when 'ogg'
+ when '.ogg'
track = Vorbis.new(file)
- when 'mp3'
+ when '.mp3'
track = ID3.new(file)
else
- raise 'Not a supported file format.'
+ raise 'File extension not recognised.'
end
- # Call methods based on the actions set.
- if $opts.has_key?('set-tag')
- track.set_tag($opts['set-tag'])
- track.write_tags
- end
-
- if $opts.has_key?('remove')
- track.remove
- track.write_tags
- elsif $opts.has_key?('remove-tag')
- track.remove_tag($opts['remove-tag'])
- track.write_tags
- end
-
- if $opts.has_key?('preview')
- track.generate
- elsif $opts.has_key?('generate')
- track.generate
- track.write_tags
- elsif $opts.has_key?('rename')
- track.rename
+ # Run specified actions.
+ actions.each do |action|
+ # Continue with next action if an error is raised.
+ begin
+ # Output action name and arguments.
+ puts ' ' + colgrn('Action: ' + action.join(' '))
+
+ # Run action.
+ case action.first
+ when :view, :remove
+ track.send(action.first)
+ when :view_tag, :set_tag, :remove_tag, :generate, :rename
+ track.send(action.first, action.last)
+ end
+ rescue RuntimeError => message
+ puts ' ' + colred('ERROR:') + ' ' + message
+ end
end
- if $opts.has_key?('view')
- track.view
- elsif $opts.has_key?('view-tag')
- track.view_tag($opts['view-tag'])
- end
+ # Finalise changes.
+ track.finalise unless options.include?(:pretend)
rescue RuntimeError => message
- warn(message)
+ puts ' ' + colred('ERROR:') + ' ' + message
end
end