Class: SubtitleShifter

Inherits:
Object
  • Object
show all
Defined in:
lib/subtitle_shifter.rb

Overview

Examples:

Manipulate subtitles from the command line

$ subtitle_shifter --help
$ subtitle_shifter --operation add --index 12 --time 2,345 source.srt dest.srt

Manipulate subtitles from within a ruby program

# This will shift all subtitles from index 12 onward by 2.345 seconds
# or 2345 milliseconds
subs = SubtitleShifter.new('mysubs.srt')
subs.parse
subs.shift(:index => 12, :time => 2345)

Shift subtitles backward

# This will shift subtitles backward, beware - you cannot shift
# subtitles backward so that they overlap the preceding subtitles.
# A RuntimeError exception will be raised if this occurs.
subs.shift(:index => 12, :time => -2345) # Simply provide a negative time value

Output subtitles once they’ve been parsed and/or manipulated

puts subs
# -- or --
subs.to_s

See Also:

Defined Under Namespace

Modules: Version

Constant Summary collapse

TIME_SEPERATOR =

The delimiter used for separating SubRip time stamps

'-->'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file, linebreak = "\r\n") ⇒ SubtitleShifter

Returns a new instance of SubtitleShifter.

Parameters:

  • file (String)

    A string of the file name

  • linebreak (String) (defaults to: "\r\n")

    A string of the linebreak pattern.



56
57
58
59
60
# File 'lib/subtitle_shifter.rb', line 56

def initialize(file, linebreak = "\r\n")
  @sub_file  = file
  @linebreak = linebreak
  @parsed_ok = false
end

Instance Attribute Details

#parsed_okObject (readonly)

A boolean flag highlighting whether or not subtitles have been parsed yet



34
35
36
# File 'lib/subtitle_shifter.rb', line 34

def parsed_ok
  @parsed_ok
end

#subtitlesObject (readonly)

Note:

I chose to implement internal representation of subtitle files as a hash and not an array, which would’ve been more efficient, as subtitles cannot be guaranteed to start at index 1 That being said, I can already think of a way to do this using an array and offset attribute

A hash of the parsed subtitles. You normally wouldn’t need to access this directly

Examples:

The format of the hash is as follows

{1 => {:start => 107,
       :end   => 5762,
       :subtitle => 'This is the first subtitle'
      },
{2 => {:start => 5890,
       :end   => 10553,
       :subtitle => 'This is the second subtitle'
      }


49
50
51
# File 'lib/subtitle_shifter.rb', line 49

def subtitles
  @subtitles
end

Instance Method Details

#parseObject

Note:

Always call only after initialising.

Note:

If your subtitle file is UTF-8 encoded, and has a Byte Order Mark as its first few bytes, the BOM will not be preserved when outputing the parsed and shifted subtitles. You probably don’t need it anyway

Parses the subtitles

Examples:

sub.parse if sub.parsed_ok

See Also:



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/subtitle_shifter.rb', line 70

def parse
  raw_text  = File.open(@sub_file, 'r').read.force_encoding('UTF-8')
  raw_text.gsub!("\xEF\xBB\xBF".force_encoding("UTF-8"), '') #Remove stupid BOM that was causing me so much grief!

  #raw_text       = IO.read @sub_file
  subtitle_parts = raw_text.split "#{@linebreak}#{@linebreak}"
  @subtitles     = {}

  subtitle_parts.each do |subtitle|
    @subtitles.update extract_sub_data subtitle
  end

  # No longer needed due to removal of BOM
  #fix_first_index   # What a hack :(
  @parsed_ok = true # Not very useful, but will help when error checking is added
end

#shift(args) ⇒ Object

Shifts subtitles forward (or backward) by a number of ms from an index

Examples:

sub.shift(:index => 42, :time => 10000) # Shift subs from index 42 onwards by 10 seconds.

Parameters:

  • :index (Integer)

    The index of the subtitle

  • :time (Integer)

    The time (in ms) by which you wish to shift the subtitles. A negative value will shift backwards.

Raises:

  • (RuntimeError)

    Raises this exception when shifting backwards if index and index-1 time’s overlap



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/subtitle_shifter.rb', line 93

def shift(args)
  first = args[:index] # used for checking first go round.
  index = first
  shift = args[:time]

  if shift < 0 # backward shift check
    time1 = @subtitles[first][:start] + shift
    time2 = @subtitles[first-1][:end]
    raise RuntimeError, 'Cannot overlap backward shift' if time2 > time1
  end

  loop do
    break unless @subtitles.has_key?(index)

    @subtitles[index][:start] += shift
    @subtitles[index][:end]   += shift

    index += 1
  end
end

#to_sObject

Outputs parsed subtitles

Raises:

  • (RuntimeError)

    Will raise this exception if an attempt is made to output the subs before parsing has taken place

See Also:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/subtitle_shifter.rb', line 117

def to_s
  raise RuntimeError, 'File has not been parsed yet' unless @parsed_ok

  output = ''

  @subtitles.sort.map do |index, sub| 
    start = ms_to_srt_time sub[:start]
    fin   = ms_to_srt_time sub[:end]

    output += "#{index}#{@linebreak}#{start} #{TIME_SEPERATOR} #{fin}#{@linebreak}#{sub[:subtitle]}#{@linebreak}#{@linebreak}"
  end

  output.chomp
end