class Sequencer::Sequence

Attributes

directory[R]
pattern[R]

Public Class Methods

new(directory, filenames) click to toggle source
# File lib/sequencer.rb, line 118
def initialize(directory, filenames)
  raise "Cannot create a Sequence with no files" if filenames.empty?
  @directory, @filenames = directory, natural_sort(filenames)
  @directory.freeze
  @filenames.freeze
  detect_gaps!
  detect_pattern!
end

Public Instance Methods

<=>(another) click to toggle source
# File lib/sequencer.rb, line 283
def <=>(another)
  to_paths <=> another.to_paths
end
bulk_rename(with_pattern, into_directory = nil, &operation) click to toggle source

Apply a bulk rename

# File lib/sequencer.rb, line 235
def bulk_rename(with_pattern, into_directory = nil, &operation)
  # Check if the pattern includes a number. If it doesnt, add one and the
  # extension
  unless with_pattern.include?("%")
    padz = last_frame_no.to_s.length
    with_pattern = [with_pattern, ".%0#{padz}d", File.extname(pattern)].join
  end
  
  rename_map = @filenames.inject({}) do | map, filename |
    frame_no = filename.scan(NUMBERS_AT_END).flatten.shift.to_i
    
    map.merge(filename => (with_pattern % frame_no))
  end
  
  destination = into_directory || directory
  
  # Ensure it's there
  if (!File.exist?(destination))
    raise "The destination #{destination} does not exist"
  end
  
  # Ensure it's a dir
  if (!File.directory?(destination))
    raise "The destination #{destination} is not a directory"
  end
  
  # Ensure we will not produce dupes
  if (rename_map.values.uniq.length != rename_map.length)
    raise "This would would produce non-unique files"
  end
  
  if (error = (rename_map.keys & rename_map.values)).any?
    raise "This would overwrite old files with the renamed ones (#{error[0..1]}.join(',')..)"
  end
  
  if (error = (Dir.entries(destination) & rename_map.values)).any?
    raise "Files that will be created by the rename are already in place (#{error[0..1]}.join(',')..)"
  end
  
  rename_map.each_pair do | from_path, to_path |
    src, dest = File.join(directory, from_path), File.join(destination, to_path)
    File.rename(src, dest)
    #yield(src, dest)
  end
  
  self.class.new(destination, rename_map.values)
end
each() { |f| ... } click to toggle source

Yields the filename of each file to the block

# File lib/sequencer.rb, line 205
def each
  @filenames.each {|f| yield(f) }
end
each_path() { |join| ... } click to toggle source

Yield each absolute path to a file in the sequence to the block

# File lib/sequencer.rb, line 210
def each_path
  @filenames.each{|f| yield(File.join(@directory, f))}
end
expected_frames() click to toggle source
# File lib/sequencer.rb, line 176
def expected_frames
  @expected_frames ||= ((@ranges[-1].end - @ranges[0].begin) + 1)
end
file_count() click to toggle source

Returns the actual file count in the sequence

# File lib/sequencer.rb, line 194
def file_count
  @file_count ||= @filenames.length
end
Also aliased as: length
first_frame_no() click to toggle source

Returns the number of the first frame in the sequence

# File lib/sequencer.rb, line 225
def first_frame_no
  @ranges[0].begin
end
gap_count() click to toggle source
# File lib/sequencer.rb, line 180
def gap_count
  @ranges.length - 1
end
gaps?() click to toggle source

Returns true if this sequence has gaps

# File lib/sequencer.rb, line 133
def gaps?
  @ranges.length > 1
end
include?(base_filename) click to toggle source

Check if this sequencer includes a file

# File lib/sequencer.rb, line 200
def include?(base_filename)
  @filenames.include?(base_filename)
end
inspect() click to toggle source
# File lib/sequencer.rb, line 142
def inspect
  '#<%s>' % to_s
end
last_frame_no() click to toggle source

Returns the number of the last frame in the sequence

# File lib/sequencer.rb, line 230
def last_frame_no
  @ranges[-1].end
end
length()
Alias for: file_count
missing_frames() click to toggle source

Returns the number of frames that the sequence should contain to be continuous

# File lib/sequencer.rb, line 189
def missing_frames
  expected_frames - file_count
end
numbered?() click to toggle source

Returns true if the files in the sequence can have numbers

# File lib/sequencer.rb, line 128
def numbered?
  @numbered ||= !!(@filenames[0] =~ NUMBERS_AT_END)
end
segment_count() click to toggle source
# File lib/sequencer.rb, line 184
def segment_count
  @ranges.length
end
single_file?() click to toggle source

Tells whether this is a single frame sequence

# File lib/sequencer.rb, line 138
def single_file?
  @filenames.length == 1
end
to_a() click to toggle source

Returns the array of filenames

# File lib/sequencer.rb, line 215
def to_a
  @filenames.dup
end
to_paths() click to toggle source

Returns paths to the files

# File lib/sequencer.rb, line 220
def to_paths
  @filenames.map{|f| File.join(@directory, f) }
end
to_s() click to toggle source
# File lib/sequencer.rb, line 163
def to_s
  return @filenames[0] if (!numbered? || single_file?)
  
  printable = unless single_file?
    @ranges.map do | r |
      "%d..%d" % [r.begin, r.end]
    end.join(', ')
  else
    @ranges[0].begin
  end
  @inspect_pattern % "[#{printable}]"
end
to_sequences() click to toggle source

If this Sequence has gaps, this method will return an array of all subsequences that it contains

s # => #<broken_seq.[123..568, 578..702].tif>
s.to_sequences # => [#<broken_seq.[123..568].tif>, #<broken_seq.[578..702].tif>]
# File lib/sequencer.rb, line 149
def to_sequences
  return [self] unless gaps?
  
  last_offset = 0
  
  @ranges.map do | frame_range |
    frames_in_seg = frame_range.end - frame_range.begin
    seg_filenames = @filenames[last_offset..(last_offset + frames_in_seg)]
    last_offset = last_offset + frames_in_seg + 1
    s = self.class.new(@directory, seg_filenames)
  end
end

Private Instance Methods

detect_gaps!() click to toggle source
# File lib/sequencer.rb, line 322
def detect_gaps!
  only_numbers = @filenames.map do | f |
    f.scan(NUMBERS_AT_END).flatten.shift.to_i
  end
  @ranges = to_ranges(only_numbers)
end
detect_pattern!() click to toggle source
# File lib/sequencer.rb, line 293
def detect_pattern!
  
  unless numbered?
    @inspect_pattern = "%s"
    @pattern = @filenames[0]
  else
    @inspect_pattern = @filenames[0].gsub(NUMBERS_AT_END) do
      ["%s", $2].join
    end
  
    highest_padding = nil
    @pattern = @filenames[-1].gsub(NUMBERS_AT_END) do
      highest_padding = $1.length
      ["%0#{$1.length}d", $2].join
    end
  
    # Look at the first file in the sequence.
    lowest_padding = @filenames[0].scan(NUMBERS_AT_END).flatten.shift.length
    if lowest_padding < highest_padding # Natural numbering
      @pattern = @filenames[0].gsub(NUMBERS_AT_END) do
        ["%d", $2].join
      end
    end
  end
  
  @inspect_pattern.freeze
  @pattern.freeze
end
natural_sort(ar) click to toggle source
# File lib/sequencer.rb, line 289
def natural_sort(ar)
  ar.sort_by {|e| e.scan(NUMBERS_AT_END).flatten.shift.to_i }
end
to_ranges(array) click to toggle source
# File lib/sequencer.rb, line 329
def to_ranges(array)
  array.compact.sort.uniq.inject([]) do | result, elem |
    result = [elem..elem] if result.length.zero?
    if [result[-1].end, result[-1].end.succ].include?(elem)
      result[-1] = result[-1].begin..elem
    else
      result.push(elem..elem)
    end
    result
  end
end