Skip to content

Instantly share code, notes, and snippets.

@mschuerig
Last active December 15, 2015 20:58
Show Gist options
  • Save mschuerig/5322030 to your computer and use it in GitHub Desktop.
Save mschuerig/5322030 to your computer and use it in GitHub Desktop.
Copy tags from OGG or MP3 files to FLAC files. Matching files are found by patterns containing their track number. Optionally rename files.
#! /usr/bin/ruby1.9.3 -w
require 'optparse'
require 'taglib'
FIELDS = %w( ALBUM ARTIST CDDB DATE GENRE DISCNUMBER TITLE TRACKNUMBER )
$dry_run = false
$tag = true
$rename = false
$quiet = false
$verbose = false
$help = false
def main
opts = OptionParser.new
opts.banner = "Usage: #{opts.program_name} [options] from_pattern to_pattern"
opts.on('-n', '--dry-run', "Don't run any commands, just print them") {
$dry_run = true
}
opts.on('-t', '--tag', "copy tags from matching files (default)") {
$tag = true
}
opts.on('-T', '--no-tag', "do not copy tags from matching files") {
$tag = false
}
opts.on('-r', '--rename', "rename from matching files") {
$rename = true
}
opts.on('-R', '--no-rename', "do not rename from matching files (default)") {
$rename = false
}
opts.on('-q', '--quiet', "Display less information") {
$quiet = true
}
opts.on('-v', '--verbose', "Display extra information") {
$verbose = true
}
opts.on('-h', '--help', "Show this message") {
$help = true
}
args = opts.parse(ARGV)
from_pattern, to_pattern = args
if $help || from_pattern.nil? || to_pattern.nil?
puts opts.help
puts
puts "Examples"
puts " # Copy tags from 1.Track.ogg to 1.Track.flac"
puts " #{opts.program_name} '%.1d.*.ogg' '%.1d.*.flac'"
puts
puts " # Copy tags from 01.Track.ogg to 01.Track.flac"
puts " #{opts.program_name} '%.2d.*.ogg' '%.2d.*.flac'"
puts
puts " # Assuming 1.Song.ogg, rename 1.Track.flac to 1.Song.flac"
puts " #{opts.program_name} -rT '%.1d.*.ogg' '%.1d.*.flac'"
exit 0
end
(1..100).each do |track|
from_file = match_pattern(from_pattern, track) or next
to_file = match_pattern(to_pattern, track) or next
if $tag
copy_tags(from_file, to_file)
end
if $rename
rename_file(to_file, from_file)
end
end
end
def match_pattern(pat, track)
candidate = pat.gsub(/([\[\]])/, '\\\\\1') % track
matches = Dir[candidate]
raise "Multiple matches for \"#{candidate}\"" if matches.size > 1
matches.first
end
def copy_tags(from_file, to_file)
puts "Copying tags from #{from_file} to #{to_file}" unless $quiet
from_fields = read_fields(from_file)
puts " Tags: #{from_fields.inspect}" if $verbose
write_fields(to_file, from_fields) unless $dry_run
end
def read_fields(file)
case File.extname(file)
when '.ogg', '.OGG'
TagLib::Ogg::Vorbis::File.open(file, false) do |f|
field_map = f.tag.field_list_map
FIELDS.each_with_object({}) do |field, fields|
value = field_map[field]
fields[field] = value.first if value && FIELDS.include?(field)
end
end
when '.mp3', '.MP3'
TagLib::MPEG::File.open(file, false) do |f|
tag = f.id3v2_tag
FIELDS.each_with_object({}) do |fld, flds|
m = fld.downcase
flds[fld] = tag.public_send(m) if tag.respond_to?(m)
end
end
else
raise ArgumentError, "Cannot extract tags from #{file}"
end
end
def write_fields(file, fields)
TagLib::FLAC::File.open(file, false) do |f|
tag = f.xiph_comment
fields.each do |key, value|
tag.add_field(key, value)
end
f.save
end
end
def rename_file(new_path, old_path)
ext = File.extname(new_path)
new_dir = File.dirname(new_path)
old_name = File.basename(old_path, '.*')
path = File.join(new_dir, "#{old_name}#{ext}").sub(/^\.\//, '')
unless path == new_path
puts "Renaming #{File.basename(new_path)} -> #{File.basename(path)}" unless $quiet
File.rename(new_path, path) unless $dry_run
end
end
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment