-
-
Save anonymous/22f423715334c756b3aa to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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