Skip to content

anonymous /CharlesFeduke secret
Created

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
# Charles Feduke
require 'optparse'
require 'ostruct'
require 'time'
class Time
def to_s
self.strftime('%H:%M:%S') + ',' + (self.usec / 1_000).to_s
end
end
class ShiftSubtitle
def initialize(kernel=Kernel,file=File)
@kernel = kernel
@file = file
@options = OpenStruct.new({
:operation => :add,
:time => "0,000",
:input_file => "",
:output_file => ""
})
end
def process_arguments(args)
opts = OptionParser.new do |opts|
opts.banner = "Usage: shiftsubtitle.rb --operation ADD|SUB --time [TIME=0,000] INPUT_FILE OUTPUT_FILE"
opts.separator ""
opts.on("-o", "--operation OPERATION", [:add, :sub], "Add (add) or subtract (sub) TIME") do |op|
@options.operation = op
end
opts.on("-t", "--time TIME", "Amount of time in SECONDS,MILLISECONDS to shift by") do |time|
@options.time = time || '0,000'
if not @options.time =~ /^\d+$|^\d+[,]+\d{1,3}$/
puts "TIME must be in #,### format."
@kernel.exit
end
@options.time = @options.time.gsub!(/,/, '.').to_f
end
opts.on_tail("-h", "--help", "Shows this message") do
puts opts
@kernel.exit
end
end
opts.parse!(args)
@options.input_file, @options.output_file = args
if @options.input_file.nil? or
@options.input_file.length == 0 or
@options.output_file.nil? or
@options.output_file.length == 0
puts "Both INPUT_FILE and OUTPUT_FILE must be specified."
@kernel.exit
end
if not @options.input_file.nil? and not @options.input_file.length == 0 and not @file.exist?(@options.input_file)
puts "INPUT_FILE must exist."
@kernel.exit
end
@options
end
def transform
count = 0
@file.open(@options.output_file, 'w') do |outfile|
@file.open(@options.input_file, 'r') do |infile|
while !infile.eof
entry = SubRipEntry.new
begin
entry.process infile
rescue EOFError
end
entry.adjust @options.operation, @options.time
entry.output outfile
outfile.puts
count += 1
end #while
end #infile
end #outfile
count
end
end
class SubRipEntry
attr_reader :identifier, :start, :end, :lines
def process(file)
@identifier = file.readline.chomp.to_i
times = file.readline.chomp
@start, @end = times.split(' --> ', 2).map { |time| Time.parse(time) }
@lines = Array.new
while (line = file.readline)
line.chomp!
break if line.length == 0
@lines.push(line)
end
end
def adjust(operation, amount)
operation = (operation == :add ? :+ : :-)
@start, @end = [@start, @end].map {|time| time.send(operation, amount)}
end
def output(file)
[@identifier, @start.to_s + ' --> ' + @end.to_s, @lines].each {|line| file.puts(line)}
end
end
def stopwatch
start = Time.now
yield
Time.now - start
end
# this prevents the block of code within from executing during unit tests
if __FILE__ == $0
count = 0
elapsed = stopwatch do
ss = ShiftSubtitle.new
ss.process_arguments ARGV
count = ss.transform
end
puts "Processed #{count} entries in #{elapsed} seconds."
end
# shiftsubtitle_spec.rb
require 'shiftsubtitle'
require 'construct'
describe ShiftSubtitle do
include Construct::Helpers
before :each do
@mock_kernel = mock(Kernel)
@mock_kernel.stub!(:exit)
@mock_file = mock(File)
@mock_file.stub!(:exist?).and_return(true)
end
it "should exit cleanly when -h is used" do
ss = ShiftSubtitle.new(@mock_kernel)
@mock_kernel.should_receive(:exit)
ss.process_arguments(["-h"])
end
it "should throw an exception when operation is not add/sub" do
ss = ShiftSubtitle.new
lambda {
ss.process_arguments(["-o", "blah"])
}.should raise_error
end
it "should only accept the appropriate #,### format for time" do
ss = ShiftSubtitle.new(@mock_kernel, @mock_file)
@mock_kernel.should_receive(:exit)
ss.process_arguments(["-t", "x"])
ss.process_arguments(["-t", "34,"])
ss.process_arguments(["-t", "3333."])
ss.process_arguments(["-t", "22,4444"])
end
it "should require that INPUT_FILE and OUTPUT_FILE be specified" do
ss = ShiftSubtitle.new(@mock_kernel)
@mock_kernel.should_receive(:exit)
ss.process_arguments([""])
ss.process_arguments(["input.txt"])
end
it "should verify the INPUT_FILE exists" do
ss = ShiftSubtitle.new(@mock_kernel)
@mock_kernel.should_receive(:exit)
ss.process_arguments(["input.txt"])
end
it "should parse correctly if expected parameters are passed" do
ss = ShiftSubtitle.new(@mock_kernel, @mock_file)
options = ss.process_arguments(["-o", "add", "-t", "3,14", "input", "output"])
options.operation.should == :add
options = ss.process_arguments(["-o", "sub", "-t", "4.344", "input", "output"])
options.operation.should == :sub
end
it "should correctly process an input file and produce an output file" do
within_construct do |construct|
construct.file('unit_test_in.srt') do
< 01:31:54,893
the government is implementing a new policy...
646
01:31:54,928 --> 01:31:57,664
In connection with a dramatic increase
in crime in certain neighbourhoods,
EOS
end
construct.file('unit_test_out.srt')
ss = ShiftSubtitle.new
ss.process_arguments(['-o', 'add', '-t', '0,500', 'unit_test_in.srt', 'unit_test_out.srt'])
ss.transform
entries = Array.new
File.open('unit_test_out.srt', 'r') do |f|
begin
while !f.eof do
entry = SubRipEntry.new
entry.process f
entries.push entry
end
rescue EOFError
f.close
end
end
entries[0].identifier.should == 645
entries[0].start.to_s.should == "01:31:51,710"
entries[0].end.to_s.should == "01:31:55,393"
entries[0].lines.length.should == 1
entries[0].lines[0].should == "the government is implementing a new policy..."
entries[1].identifier.should == 646
entries[1].start.to_s.should == "01:31:55,428"
entries[1].end.to_s.should == "01:31:58,164"
entries[1].lines.length.should == 2
entries[1].lines[0].should == "In connection with a dramatic increase"
entries[1].lines[1].should == "in crime in certain neighbourhoods,"
end # within
end
end
# subripentry_test.rb
require 'test/unit'
require 'construct'
require 'shiftsubtitle'
class Pathname
# windows has problems with temp files created by Ruby
# http://redmine.ruby-lang.org/issues/show/1494
def rmtree
nil
end
end
class SubRipEntryTest < Test::Unit::TestCase
include Construct::Helpers
def test_process
entry = SubRipEntry
within_construct do |construct|
construct.file('unit_test.srt') do
< 01:31:57,664
In connection with a dramatic increase
in crime in certain neighbourhoods,
EOS
end
entry = SubRipEntry.new
file = File.open('unit_test.srt')
begin
entry.process file
rescue EOFError
file.close
end
assert_equal 646, entry.identifier
assert_equal '01:31:54,928', entry.start.to_s
assert_equal '01:31:57,664', entry.end.to_s
assert_equal 2, entry.lines.length
assert_equal 'In connection with a dramatic increase', entry.lines[0]
assert_equal 'in crime in certain neighbourhoods,', entry.lines[1]
end
end
def test_adjust
entry = SubRipEntry
within_construct do |construct|
construct.file('unit_test.srt') do
< 01:31:57,664
In connection with a dramatic increase
in crime in certain neighbourhoods,
EOS
end
entry = SubRipEntry.new
file = File.open('unit_test.srt')
begin
entry.process file
rescue EOFError
file.close
end
assert_equal '01:31:54,928', entry.start.to_s
assert_equal '01:31:57,664', entry.end.to_s
entry.adjust(:add, 2.500)
assert_equal '01:31:57,428', entry.start.to_s
assert_equal '01:32:00,164', entry.end.to_s
entry.adjust(:sub, 2.500)
assert_equal '01:31:54,928', entry.start.to_s
assert_equal '01:31:57,664', entry.end.to_s
end
end
def test_output
src, dst = SubRipEntry
within_construct do |construct|
construct.file('unit_test_in.srt') do
< 01:31:57,664
In connection with a dramatic increase
in crime in certain neighbourhoods,
EOS
end
src = SubRipEntry.new
file = File.open('unit_test_in.srt')
begin
src.process file
rescue EOFError
file.close
end
construct.file('unit_test_out.srt')
File.open('unit_test_out.srt', 'w') do |f|
src.output(f)
end
dst = SubRipEntry.new
File.open('unit_test_out.srt', 'r') do |f|
begin
dst.process f
rescue EOFError
f.close
end
end
assert_equal 646, dst.identifier
assert_equal '01:31:54,928', dst.start.to_s
assert_equal '01:31:57,664', dst.end.to_s
assert_equal 2, dst.lines.length
assert_equal 'In connection with a dramatic increase', dst.lines[0]
assert_equal 'in crime in certain neighbourhoods,', dst.lines[1]
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.