jbutkowski
5/26/2013 - 7:55 AM

vname.rb

#! /usr/bin/env ruby -r ftools
# "vname" by Henrik Nyh <http://henrik.nyh.se> on 2010-05-28 under the MIT license.
#  
#  Usage:
#  
#    vname PATTERN
#  
#  Prompts to rename files in the working directory matching the PATTERN into this pattern:
#  
#    X-Files - S01E03 - Pilot.avi
#  
#  Useful for e.g. renaming TV series episodes for Plex.
#  
#  The pattern is a combination of whichever of these you like:
#  
#    :s  - season number (defaults to 1)
#    :e  - episode number (also handles intervals like 01-02, defaults to 1)
#    :p  - programme/show name
#    :t  - episode title
#    ... - any uninteresting sequence of characters
#  
#  The pattern doesn't have to match the full filename, only part. Don't mention the extension.
#
#  You can use e.g. ":s:e" for "203.avi" to get "S02E03". In that case it assumes the season is a single digit.
#
#  You may also specify programme and season explicitly as second and third command-line arguments, to get around
#  tricky names. E.g. "tpz-deadwood301.avi" makes it difficult to separate programme and season, so you could do
#
#    vname tpz-deadwood3:e Deadwood 3
#  
#  Example session:
#  
#    $ touch "X-Files [1x01] Pilot- DVDRip.avi"
#    $ touch "X-Files [1x02] Ett kul avsnitt- DVDRip.avi"
#    $ ls X-Files*
#    X-Files [1x01] Pilot- DVDRip.avi           X-Files [1x02] Ett kul avsnitt- DVDRip.avi
#    $ vname ":p [:sx:e] :t-"
#    
#    From:  X-Files [1x01] Pilot- DVDRip.avi
#    To:    X-Files - S01E01 - Pilot.avi
#    
#    From:  X-Files [1x02] Ett kul avsnitt- DVDRip.avi
#    To:    X-Files - S01E02 - Ett kul avsnitt.avi
#    
#    Rename 2 files (y/n - default: n)? y
#    
#    Renamed:   X-Files [1x01] Pilot- DVDRip.avi   ==>   X-Files - S01E01 - Pilot.avi
#    Renamed:   X-Files [1x02] Ett kul avsnitt- DVDRip.avi   ==>   X-Files - S01E02 - Ett kul avsnitt.avi
#    
#    Done.
#    $ ls X-Files*
#    X-Files - S01E01 - Pilot.avi           X-Files - S01E02 - Ett kul avsnitt.avi


# Usage instructions.
if ARGV.empty?
  puts File.readlines(__FILE__).grep(/^#  /)
  exit
end

pattern = ARGV[0]
global_programme = ARGV[1]
global_season = ARGV[2]

patterns = pattern.split(/(:[pest]|\.\.\.)/u)

parts = patterns.map { |part|
  case part
  when ':p':  /(.+)/
  when ':e':  /(\d+(?:-\d+)?)/
  when ':s':  /(\d+?)/
  when ':t':  /(.+)/ 
  when '...': /.*/
  else Regexp.escape(part)
  end
}
parts << /.*(\.\w+)$/  # Extension.

re = parts.join
capture_groups = patterns.select { |part| part.match(/:[pest]/) }
renamings = []

filenames = Dir["*"]
filenames.each do |old_filename|
  next unless (match = old_filename.match(re))
  
  captures = match.captures
  extension = captures.pop

  mapping = captures.to_enum.with_index.inject({}) {|hash, (value, index)| hash.merge( capture_groups[index] => value ) }
  programme, episode, season, title = mapping.values_at(*%w[:p :e :s :t])
  programme = global_programme || programme
  season = global_season || season

  new_filename_parts = []
  new_filename_parts << programme
  new_filename_parts << format("S%02dE%02d", (season ? season.to_i : 1), (episode ? episode.to_i : 1))
  new_filename_parts << title
  new_filename = new_filename_parts.compact.join(' - ') + extension
  
  renamings << [old_filename, new_filename]

  puts
  puts "From:  #{old_filename}"
  puts "To:    #{new_filename}"

end

if renamings.empty?

  puts "Nothing matches that pattern."

else

  puts
  print "Rename #{renamings.length} file#{'s' if renamings.length > 1} (y/n - default: n)? "
  answer = STDIN.gets
  exit unless answer.match(/y/i)

  puts
  renamings.each do |old_filename, new_filename|
    File.mv(old_filename, new_filename)
    puts "Renamed:   #{old_filename}   ==>   #{new_filename}"
  end
  puts
  puts "Done."

end