Skip to content

Instantly share code, notes, and snippets.

@elomatreb
Last active January 11, 2018 03:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elomatreb/41d023d0939b9743deb6ad1b9bc39322 to your computer and use it in GitHub Desktop.
Save elomatreb/41d023d0939b9743deb6ad1b9bc39322 to your computer and use it in GitHub Desktop.
Renaming script I use for managing episodes of downloaded series batches
#!/usr/bin/env ruby
require "fileutils"
require "highline"
# Characters illegal in most filenames
ILLEGAL_CHARS = %w[< > : " / \\ | * ?]
cli = HighLine.new
files = ARGV.select {|f| File.file? f }.freeze
files.each do |f|
next if File.basename(f) == f
warn "'#{f}' is not the same as its basename, aborting. (Not in current directory?)"
exit 1
end
puts HighLine.color("Processing #{files.size} files", :green)
# Set the prefix, typically the show name
prefix = cli.ask("Prefix (show name)? ") do |q|
q.validate = lambda do |a|
a.match?(/\A[[:print:]]+\z/) && !a.match?(Regexp.union(ILLEGAL_CHARS))
end
q.readline = true
q.responses[:not_valid] = "The prefix may not contain illegal characters (#{ILLEGAL_CHARS.join(", ")})"
end
# Get the season (or disable)
season = cli.ask("Season (Leave empty for no season)? ") do |q|
q.validate = -> (a) { a == "" || a.to_i > 0 }
q.readline = true
q.answer_type = -> (a) { a == "" ? nil : Integer(a, 10) }
q.responses[:not_valid] = "You must enter a positive integer (> 0) or leave this empty."
end
puts "-------\n\n"
new_filenames = files.map do |f|
puts "Processing file: '#{HighLine.color(f, :bold)}'"
guessed_id = f.match(/S?\d+E(\d+)/i) {|m| Integer(m[1], 10) rescue nil } ||
f.match(/(\d\d)/) {|m| Integer(m[1], 10) rescue nil }
episode_id = cli.ask(" Episode ID? ") do |q|
# Either single digit (will be padded), double digit, or double-episode with
# two double-digits joined by a &. Supports decimals.
q.validate = /\A\d(?:\.\d)?\z|\A\d\d(?:\.\d)?(?:&\d\d(?:\.\d)?)?\z/
q.default = format "%02d", guessed_id
q.readline = true
q.answer_type = lambda do |a|
# Prefix the answer with a 0 if it was given as a single
return a unless a.match?(/\A\d(?:\.\d)?\z/)
"0" + a
end
q.responses[:not_valid] = "The episode ID must be in single digit form (D or D.D+), double digit form (DD or DD.D+), or double-double digit form (for double episodes, DD&DD DD.D+&DD.D+). Decimals are optional."
end
episode_title = cli.ask("Episode title? ") do |q|
q.validate = lambda do |a|
a.match?(/\A[[:print:]]+\z/) && !a.match?(Regexp.union(ILLEGAL_CHARS))
end
q.readline = true
q.responses[:not_valid] = "The title may not contain illegal characters (#{ILLEGAL_CHARS.join(", ")})"
end
puts "-------\n\n" # Separator
extension = f[/.*\.(.+?)\z/, 1] # File extension
if season
format "%s - S%02dE%s - %s.%s", prefix, season, episode_id, episode_title, extension
else
format "%s - %s - %s.%s", prefix, episode_id, episode_title, extension
end
end
changes = files.zip new_filenames
left = [files.map(&:length).max + 1, 10].max
right = [new_filenames.map(&:length).max + 1, 5].max
puts HighLine.color(" Original ".ljust(left), :bold) + "|" + HighLine.color(" New", :bold)
puts "-" * left + "+" + "-" * right
puts changes.map {|old, new| old.rjust(left-1) + " → " + new }.join "\n"
puts HighLine.color(" → #{changes.select {|new, old| old != new }.size} changes.", :bold, :white)
apply = cli.agree(" Apply these changes? [y/n] ") do |q|
q.character = :getc
end
puts # getc doesn't do a proper newline
if apply
changes.each {|from, to| FileUtils.mv from, to }
puts "done."
else
puts "Not applying; exiting"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment