class SrtValidator::File

Attributes

lines[W]

Public Class Methods

parse(input, options = {}) click to toggle source
# File lib/srt_validator/file.rb, line 3
def self.parse(input, options = {})
  @debug = options.fetch(:debug, false)
  if input.is_a?(String)
    parse_string(input)
  elsif input.is_a?(::File)
    parse_file(input)
  else
    raise "Invalid input. Expected a String or File, got #{input.class.name}."
  end
end
parse_file(srt_file) click to toggle source
# File lib/srt_validator/file.rb, line 14
def self.parse_file(srt_file)
  parse_string ::File.open(srt_file, 'rb') { |f| srt_file.read }
end
parse_string(srt_data) click to toggle source
# File lib/srt_validator/file.rb, line 18
def self.parse_string(srt_data)
  result = new
  line = Line.new
  last_line = Line.new
  count = 0

  split_srt_data(srt_data).each_with_index do |str, index|
    begin
      if str.strip.empty?
        result.lines << line unless line.empty?
        line = Line.new
      elsif !line.error
        if line.sequence.nil?
          count += 1
          line.sequence = str.to_i
          unless count == line.sequence
            line.error = "#{index}, sequence_number, [#{line.sequence}]"
            $stderr.puts line.error if @debug
          end
        elsif line.start_time.nil?
          if mres = str.match(/(?<start_timecode>[^[[:space:]]]+) -+> (?<end_timecode>[^[[:space:]]]+) ?(?<display_coordinates>X1:\d+ X2:\d+ Y1:\d+ Y2:\d+)?/)
            line.start_time = Parser.timecode(mres["start_timecode"])
            line.end_time = Parser.timecode(mres["end_timecode"])

            if line.start_time.nil? || !last_line.empty? && last_line.end_time > line.start_time
              line.error = "#{index}, start_timecode, [#{mres["start_timecode"]}]"
              $stderr.puts line.error if @debug
            end

            if line.end_time.nil? || line.start_time > line.end_time
              line.error = "#{index}, end_timecode, [#{mres["end_timecode"]}]"
              $stderr.puts line.error if @debug
            end

            if mres["display_coordinates"]
              line.display_coordinates = mres["display_coordinates"]
            end

            last_line = line
          else
            line.error = "#{index}, wrong_times, [#{str}]"
            $stderr.puts line.error if @debug
          end
        else
          if /\d{1,}:\d{1,2}:\d{1,2},\d{1,3} --> \d{1,}:\d{1,2}:\d{1,2},\d{1,3}/.match(str.strip) && /\d{1,}/.match(line.text.last)
            line.error = "#{index}, break_line, [#{str}]"
            $stderr.puts line.error if @debug
          end

          line.text << str.strip
        end
      end
    rescue
      line.error = "#{index}, general_error, [#{str}]"
      $stderr.puts line.error if @debug
    end
  end
  result
end
split_srt_data(srt_data) click to toggle source

Ruby often gets the wrong encoding for a file and will throw errors on `split` for invalid byte sequences. This chain of fallback encodings lets us get something that works.

# File lib/srt_validator/file.rb, line 81
def self.split_srt_data(srt_data)
  begin
    srt_data.split(/\n/) + ["\n"]
  rescue
    begin
      srt_data = srt_data.unpack("C*").pack("U*")
      srt_data.force_encoding('utf-8').split(/\n/) + ["\n"]
    rescue
      srt_data.force_encoding('iso-8859-1').split(/\n/) + ["\n"]
    end
  end
end

Public Instance Methods

append(options) click to toggle source
# File lib/srt_validator/file.rb, line 94
def append(options)
  if options.length == 1 && options.values[0].class == self.class
    reshift = Parser.timecode(options.keys[0]) || (lines.last.end_time + Parser.timespan(options.keys[0]))
    renumber = lines.last.sequence

    options.values[0].lines.each do |line|
      lines << line.clone
      lines.last.sequence += renumber
      lines.last.start_time += reshift
      lines.last.end_time += reshift
    end
  end

  self
end
errors() click to toggle source
# File lib/srt_validator/file.rb, line 247
def errors
  lines.collect { |l| l.error if l.error }.compact
end
lines() click to toggle source
# File lib/srt_validator/file.rb, line 243
def lines
  @lines ||= []
end
split(options) click to toggle source
# File lib/srt_validator/file.rb, line 110
def split(options)
  options = { :timeshift => true, :renumber => true }.merge(options)

  split_points = []

  if (options[:at])
    split_points = [options[:at]].flatten.map{ |timecode| Parser.timecode(timecode) }.sort
  elsif (options[:every])
    interval = Parser.timecode(options[:every])
    max = lines.last.end_time
    (interval..max).step(interval){ |t| split_points << t }
  end

  if (split_points.count > 0)
    split_offsprings = [File.new]

    reshift = 0
    renumber = 0

    lines.each do |line|
      if split_points.empty? || line.end_time <= split_points.first
        cloned_line = line.clone
        cloned_line.sequence -= renumber if options[:renumber]
        if options[:timeshift]
          cloned_line.start_time -= reshift
          cloned_line.end_time -= reshift
        end
        split_offsprings.last.lines << cloned_line
      elsif line.start_time < split_points.first
        cloned_line = line.clone
        cloned_line.sequence -= renumber if options[:renumber]
        if options[:timeshift]
          cloned_line.start_time -= reshift
          cloned_line.end_time = split_points.first - reshift
        end
        split_offsprings.last.lines << cloned_line

        renumber = line.sequence - 1
        reshift = split_points.first
        split_points.delete_at(0)

        split_offsprings << File.new
        cloned_line = line.clone
        cloned_line.sequence -= renumber if options[:renumber]
        if options[:timeshift]
          cloned_line.start_time = 0
          cloned_line.end_time -= reshift
        end
        split_offsprings.last.lines << cloned_line
      else
        renumber = line.sequence - 1
        reshift = split_points.first
        split_points.delete_at(0)

        split_offsprings << File.new
        cloned_line = line.clone
        cloned_line.sequence -= renumber if options[:renumber]
        if options[:timeshift]
          cloned_line.start_time -= reshift
          cloned_line.end_time -= reshift
        end
        split_offsprings.last.lines << cloned_line
      end
    end
  end

  split_offsprings
end
timeshift(options) click to toggle source
# File lib/srt_validator/file.rb, line 179
def timeshift(options)
  if options.length == 1
    if options[:all] && (seconds = Parser.timespan(options[:all]))
      lines.each do |line|
        line.start_time += seconds
        line.end_time += seconds
      end
    elsif (original_framerate = Parser.framerate(options.keys[0])) && (target_framerate = Parser.framerate(options.values[0]))
      time_ratio = original_framerate / target_framerate
      lines.each do |line|
        line.start_time *= time_ratio
        line.end_time *= time_ratio
      end
    end
  elsif options.length == 2
    origins, targets = options.keys, options.values

    [0,1].each do |i|
      if origins[i].is_a?(String) && Parser.id(origins[i])
        origins[i] = lines[Parser.id(origins[i]) - 1].start_time
      elsif origins[i].is_a?(String) && Parser.timecode(origins[i])
        origins[i] = Parser.timecode(origins[i])
      end

      if targets[i].is_a?(String) && Parser.timecode(targets[i])
        targets[i] = Parser.timecode(targets[i])
      elsif targets[i].is_a?(String) && Parser.timespan(targets[i])
        targets[i] = origins[i] + Parser.timespan(targets[i])
      end
    end

    time_rescale_factor = (targets[1] - targets[0]) / (origins[1] - origins[0])
    time_rebase_shift = targets[0] - origins[0] * time_rescale_factor

    lines.each do |line|
      line.start_time = line.start_time * time_rescale_factor + time_rebase_shift
      line.end_time = line.end_time * time_rescale_factor + time_rebase_shift
    end
  end

  if lines.reject! { |line| line.end_time < 0 }
    lines.sort_by! { |line| line.sequence }
    lines.each_with_index do |line, index|
     line.sequence = index + 1
     line.start_time = 0 if line.start_time < 0
    end
  end
end
to_s(time_str_function=:time_str) click to toggle source
# File lib/srt_validator/file.rb, line 228
def to_s(time_str_function=:time_str)
  lines.map { |l| l.to_s(time_str_function) }.join("\n")
end
to_webvtt() click to toggle source
# File lib/srt_validator/file.rb, line 232
    def to_webvtt
      header = <<eos
WEBVTT
X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000

eos
      header + to_s(:webvtt_time_str)
    end