RPCFN: Shift Subtitle (#1)

by on September 24, 2009

Ruby Programming Challenge For Newbies

RPCFN: Shift Subtitle (#1)

By Fabio Akita

After a very encouraging response to our poll from YOU, the readers of the RL blog, RL is happy to announce the first-ever fortnightly ( bi-weekly / every 14 days) “Ruby Programming Challenge For Newbies (RPCFN)” in Ruby. Thanks to YOU, the Ruby community, people like Fabio Akita and companies like Locaweb who make all of this possible.

About Fabio Akita

Fabio AkitaFabio Akita is a Brazilian Rails enthusiast, also known online as “AkitaOnRails”. He regularly write posts on his own blog and had published the very first book tailored for the Brazilian audience called “Repensando a Web com Rails”.

He is now a full-time Ruby on Rails developer working as Project Manager at Locaweb, Brazil. He’s also the creator of the “Rails Summit Latin America“, the largest international Rails event in South America.

Fabio has this to say about the challenge:

If you’re learning a new language such as Ruby, it is important that you practice it. And the best way to start is by scratching your own itch. Anything goes. It’s not unusual to start by writing simple command line scripts to help out your everyday routine. That’s why I thought of a very trivial exercise in the first challenge. It should demand that you know the basics for a variety of Ruby subjects such as regular expressions, file manipulation, time calculation and so on. The only way to achieve mastery is by practice. So let’s get started!

Sponsor

UK based Passenger Hosting

1st Easy Limited are delighted to have been given the opportunity to support the work of Satish Talim and his team at RubyLearning.

Taking part in the Ruby Programming Challenge? You’re welcome to take advantage of the free Ruby on Rails hosting trials that 1st Easy offer: simply register your details, and a full-featured account is yours to do with as you please for one month. Once the trial is over, you can transfer your work to a paid account, or walk away with no questions asked!

Prizes

  • The person with the best Ruby solution (if there is a tie between answers, then the one who posted first will be the winner) will be awarded any one of PeepCode’s Ruby on Rails screencasts.
  • The other prize, selected randomly amongst the remaining working Ruby solutions, will be awarded any one of Pragmatic’s The Ruby Object Model and Metaprogramming screencasts.

The two persons who win, can’t win again in the next immediate challenge but can still participate.

The Ruby Challenge

RPCFN

Difficulty: Ruby beginner.

Goals: Basic control over Ruby elements, specially command line scripting.

Description: There are several ways to subtitle a movie nowadays, and one of the most well known format is the SubRip format (http://en.wikipedia.org/wiki/SubRip). It has entries like these:

645
01:31:51,210 --> 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,

Each line has an increasing integer identification, then comes the time range (start and end time) in the format “hours:minutes:seconds,milliseconds”. The decimal separator used is the comma. Finally there are the subtitles themselves and a line break marks the end of an entry.

Sometimes the timing is shifted for a small amount, 2 or 3 seconds. Then comes the trouble when you need to shift everything a few seconds back or ahead.

The goal is to create a small command line script in Ruby that will read an SRT file, and output another one with the new calculated times.

So, for example, if I want to shift everything 2,500 (2 seconds and 500 milliseconds) ahead, I would start with this:

01:32:04,283 --> 01:32:07,769

and end up with:

01:32:06,783 --> 01:32:10,269

The command line should accept arguments such as:

shift_subtitle --operation add --time 02,110 input_file output_file

This means “--operation” can accept either ‘add’ or ‘sub’ to add or subtract times. The “--time” will accept the amount of time to shift in the format 11,222 where “11″ is the amount of seconds and “222″ the amount of milliseconds.

Requirements: This has to be a pure Ruby script, using only the Ruby Standard Libraries (meaning, no external Gems).

It has to implement “optparse” to parse the command line arguments.

As an observation, bear in mind that the first thing that you might attempt will look like this:

a = Time.at(04,283)
b = a + 2.500
puts b.usec
=> 500283

This is wrong, the proper result should’ve been “783″ (as in the example in the previous section). So it means that you will have to find another way out.

Extras (Optional): If you want:

  • It would be interesting to exercise the process of a Gem creation. So you would have to package your script.
  • Another thing that would be good is to have RSpec unit tests covering your code, to exercise software development best practices.

(Note that the above two points are optional and not a requirement).

How to Enter the Challenge

It’s free and registration is not required. You can enter the challenge just by posting the following as a comment to this blog post:

  1. Your name:
  2. Email address (will not be published):
  3. Brief description of what you do (will not be published):
  4. Country of Residence:
  5. Your Solution (i.e. Ruby code): Prefix your code with <pre> tag and suffix it with </pre> tag.
  6. Code works with Ruby 1.8 / 1.9 / Both:
  7. Explanation (if any):
  8. Test cases (if any):

Note:

  • You may provide the URL of your source code, in case it is hosted on GitHub.
  • All solutions posted would be hidden to allow users to come up with their own solutions.
  • You should post your entries before midnight of 4th Oct. 2009 (Indian Standard Time). No new solutions will be accepted from 5th to 8th Oct. 2009.
  • On Monday, 5th Oct. 2009 all the solutions will be thrown open for everyone to see and comment upon.
  • The winning entries will be announced on this blog. The winners will be sent their prizes by email.

More details on the RPCFN?

Please refer to the RPCFN FAQ for answers to the following questions:

Donations

RPCFN is entirely financed by RubyLearning and sometimes sponsors, so if you enjoy solving Ruby problems and would like to give something back by helping with the running costs then any donations are gratefully received.

Click here to lend your support to: Support RubyLearning With Some Love and make a donation at www.pledgie.com !

Acknowledgements

Special thanks to:

Questions?

Contact Satish Talim at satish.talim@gmail.com OR if you have any doubts / questions about the challenge (the current problem statement), please post them as comments to this post and the author will reply asap.

The Participants

There are two categories of participants. Some are vying for the prize and some are participating for the fun of it. The participants were:

In the competition

  1. Felipe Giotto, Brazil
  2. Eduardo, Brazil
  3. Kalle Lindström, Sweden – declared winner
  4. Robison WR Santos, Brazil
  5. Aldric, USA
  6. Akshay Gupta, India
  7. Fabio Kreusch, Brazil
  8. Chris Jones, USA
  9. Chuck Ha, USA
  10. Milan Dobrota, Serbia
  11. Parag Shah, India
  12. Hugo Figueiredo, Brazil
  13. John McDonald, USA
  14. Felipe Elias Philipp, Brazil – declared winner
  15. Charles Feduke, USA
  16. Hari Rajagopal, USA
  17. Brad O’Connor, Australia
  18. Oliver, UK
  19. Jacob Lichner, USA
  20. Todd Huss, USA
  21. Antonio, Canada
  22. Sriram Varahan, India
  23. Giordano Scalzo, Italy
  24. Phil Kates, USA

Just for Fun

  1. Michael Kohl, Austria
  2. Rodrigo Rosenfeld Rosas, Brazil
  3. Dominik Honnef, Germany
  4. Mike Hodgson, Canada

The Winners

Winners

Congratulations to the winners of this Ruby Challenge. They are:

Next Challenge

RPCFN: Average Arrival Time For A Flight (#2) by Chris Strom.

Update

  • The Challenge is now closed. Fabio Akita has a working solution to this problem. This is not a “perfect” or the sole “correct” solution, but just one way of doing it. Fabio is thankful to Satoshi Asakawa for using one of his ideas in this implementation.

Technorati Tags: , , , , ,

Posted by Satish Talim

Follow me on Twitter to communicate and stay connected

{ 126 comments… read them below or add one }

Raven September 25, 2009 at 1:27 am

Seems a bit more than the beginner level.

Reply

Michael Kohl September 25, 2009 at 11:52 pm

How so? If you break it down into small tasks, it’s a trivial exercise. Think about it: all you have to do is this (more or less):

  • open a file
  • find lines that follow a specific pattern
  • extract time stamps
  • change them
  • write a new file

Hint

Tackle it in small pieces and you won’t feel as overwhelmed. Also the step by step description gives you a good idea which classes might be useful (File, RegEx, Time). True, optparse might be a bit confusing at first, but Satish posted very good links for that!

Don’t be discouraged, if you are willing to read some docs, this challenge should easily be solvable with the knowledge presented in the core course! :-)

Reply

Raven October 8, 2009 at 8:46 pm

Ok I will finish the RubyLearning course first, then come back and try these. In week two (when this was posted) we were no where near these concepts.

Reply

AkitaOnRails September 25, 2009 at 3:16 am

Nice :-) And for the students that are going to participate: the subtitle shifting algorithm itself is very easy (you’re going to find several different ways of doing it), but I am more interested in the whole other peripheral stuff such as project organization, command line option parsing, gem spec, testing. Take your time to study all those areas as they are all going to be useful for you in the future.

Reply

Michael September 25, 2009 at 4:17 pm

Fabio you mention that you are interested in solutions that cover project organization, command line option parsing, gem spec and testing. Is a Ruby newbie expected to know all that? What if I just submit my solution that solves the subtitle shifting algorithm problem – will my solution be not accepted?

I understand and appreciate when you say that these areas will be useful for me in the future. But for now?

Reply

Aldric September 27, 2009 at 5:48 am

According to this, I think a Ruby newbie is expected to -learn- this in two weeks ;-) At least the basics. I, for one, intend to start working on this aspect of Ruby/programming which I’ve ignored too long.

Reply

Will September 25, 2009 at 8:45 am

I’ve been looking at the challenge for a couple of hours on and off today, and I’ll at least say this: I’ve learned how little I know!

Reply

Will September 28, 2009 at 8:33 am

I was a little concerned and frustrated at first, but I remembered we’ve got two weeks to work on this, and once I started breaking down the problem bit by bit, I realized this is manageable. I’m actually getting somewhere sort of. So, anyone who was discouraged, don’t give up!

Anyway, OptionParser is awesome! That’s where I’m starting from in my script. If you haven’t looked closely at it yet, I recommend doing so. Look at the tutorials Satish linked to as well as the standard documentation. Those three combined provide a great jumping off point for your script.

Reply

Jim September 25, 2009 at 8:47 am

The challenge is classified ‘Beginner’. Ha! Seems like one’s Ruby would have to be sharp in order to cut through the ‘optparse’ jungle.

Reply

Raven September 25, 2009 at 8:49 am

If this is a challenge for new Rubyists, then I am not sure where I stand, as I thought I was a “newbie”.

That said, I would like to at least attempt it.

Fabio, the file format used is for Windows only I wonder if that matters since I am on Linux.

Reply

AkitaOnRails September 25, 2009 at 8:55 am

@Raven if you are talking about the “/” vs “\”, research the documentation of methods such as File.expand_path and File.join to make it independent.

Reply

Rajiv B. September 25, 2009 at 11:32 am

Fabio, you mention in your spec that:

“This has to be a pure Ruby script, using only the Ruby Standard Libraries (meaning, no external Gems). It has to implement “optparse” to parse the command line arguments.”

Isn’t “optparse” an external gem? So is “optparse” essential in our solution?

Reply

Satish Talim September 25, 2009 at 11:58 am
Raven September 25, 2009 at 2:25 pm

optparse is within the Ruby Libraries.. therefore not an added gem.

Reply

nofxx September 25, 2009 at 5:00 pm

Hehe, my first gem was subtitle related:
http://github.com/nofxx/subtitle_it
It’s ugly but works with a lot of formats, even .ASS files! ;)

Reply

Michael Kohl September 25, 2009 at 11:56 pm

Solution (#1):

I am from Austria.

Here’s an example solution (not participating in the challenge):

Solution: https://gist.github.com/9d048d9164fda984e2af

Reply

Felipe Giotto September 26, 2009 at 12:47 am

Solution (#2):

I am from Brazil.

My code runs with ruby 1.8.6. After a lot of ‘googling’, I’ve found that with Ruby 1.9, i could just use strftime with %L parameter, being able to get the miliseconds of the timestamp. The script would be simpler. But I wanted to make a script compatible with the older version (also because I don’t have 1.9 installed on this machine :D ). Anyway, I think it will run in ruby 1.9 too. I’ve never user OptionParser before, maybe my script is a little “ugly”, but, anyway…

There’s three files. One is the “core” (srt.rb), other is the test suite (srt_test.rb) and the other is the “runner” script.

This is my solution (there’s many files): https://gist.github.com/0d72bde27e08332c6df4

Hope you understand and like my script! I’m a big fan of your site! :D

Reply

AkitaOnRails October 5, 2009 at 12:41 pm

He did the natural course of thinking. The only thing I would expect is about dealing with the files. He is reading the input all into memory whereas the normal pattern is to stream it line by line. The algorithm itself, while laborious and not the most elegant, does indeed work correctly. The test file itself did not work for me.

Reply

Felipe Giotto October 5, 2009 at 4:31 pm

Actually, my script seems to be html escaped! When I posted it, there was a lot of ”, now there’s many ‘<’ and ‘>’… Look at the strings for the subtitles, the inheritance of the Test::Unit::TestCase class… The download link is not working too… What should we do now? :D

Reply

Michael Kohl October 5, 2009 at 12:46 pm

Even though the main algorithm may not be the most elegant, the layout of the solution (logic, runner, test) is quite nice.

Reply

Rodrigo Rosenfeld Rosas September 26, 2009 at 6:16 am

“It has to implement “optparse” to parse the command line arguments.”

This is not clear to me… Does it mean we should use “optparse” from standard lib, or implement the option parser?

Reply

Satish Talim September 26, 2009 at 6:54 am

Just use “optparse” from the standard lib.

Reply

Eduardo September 26, 2009 at 8:30 pm

Solution (#3):

I am from Brazil.

Solution: https://gist.github.com/065f4ed31b94bc72ffd1

Reply

Michael Kohl October 5, 2009 at 12:50 pm

Readability is a problem. The method names are as terse as they can be, there’s quite a few “magic numbers” and more regex than need be. Just some of my suggestions for Eduardo.

Reply

AkitaOnRails October 5, 2009 at 12:53 pm

The code is not readable and very “hacky”. Feels more like a bash script than a Ruby program.

Reply

HugoLnx September 27, 2009 at 2:51 am

Hello! Congratulations for the event.xD
In case of the commands, the user will input a subtitle first, writing,for example:
input_file “c:\subtitle.str”

and later he will write…
if he’s want change the load file, adding 2,10 for example:
shift_subtitle –operation add — time 02,010

if he’s want save in other file adding 2,10 for example:
output_file “subtitle2.srt” –operation add — time 02,010

I just need to clarify this.
[Im brazilian, my english isnt very good, sorry.xD]

Reply

Andy Henson September 27, 2009 at 4:46 am

If you’re looking for a good source of srt files, try http://www.opensubtitles.org

Reply

HugoLnx September 27, 2009 at 7:33 am

Oo?
No, no, I am confused about the commands of the optparser.xD

Reply

Satish Talim September 27, 2009 at 8:27 am

I have given 2 links to tutorials on “optparse” here -
http://rubylearning.com/blog/2009/09/24/rpcfn-shift-subtitle-1/#comment-119306

Check them out.

Reply

HugoLnx September 27, 2009 at 8:47 am

I see these tutorials, but my doubt is about what each command has to do(shift_subtitle –operation add –time 02,110 input_file output_file). If you show me some examples will facilitate very much.xD
Thanks

Reply

AkitaOnRails September 27, 2009 at 7:06 pm

Sure, –operation should receive either ‘add’ or ‘sub’(tract). –time should receive an amount of time to either add or subtract (according to the –operation option).

Hint

So, for instance if I do “–operation sub –time 03,540″ this means “subtract 3 seconds and 540 milliseconds from each time in the subtitle file”. Or if I do “–operation add –time 02,400″ this means “add 2 seconds and 400 milliseconds to each range of times in each line of the subtitle file”.

Reply

Kalle Lindström September 27, 2009 at 6:06 pm

HugoLnx it works like this:

shift_subtitle is the name of your script that you call from the command line with the Ruby interpreter like this: ruby shift_subtitle.rb

Then you have the options –operation and –time. –operation specifies if the script should increase or decrease the time and –time specifies the amount of time. –operation is followed by either “add” or “sub” and –time is followed by the time like this “[seconds],[milliseconds]”

Hint

So if you want to add 1 minute you type ruby shift_subtitle.rb –operation add –time 1,000, and if you want to subtract 500 milliseconds you type ruby shift_subtitle.rb –operation add –time 0,500. You need to use optparser to implement this behavior.

Then the final two arguments are the files the script works with. Just as it sounds, input_file is original file that should have it’s timings modified and output file is the new file that is created with the new timings.

So if you have a file called test.srt and you want to increase the time with 5 seconds and save the result in a new file named result.srt you would type: ruby shift_subtitle.rb –operation add –time 5,000 test.srt result.srt

Reply

Kalle Lindström September 27, 2009 at 7:39 pm

Solution (#4) – Winner:

Country of Residence: Sweden

My Solution (i.e. Ruby code): https://gist.github.com/fa3ec5afe7b19d22c44a

Code works with both Ruby 1.8 and 1.9

Explanation: I decided to read the whole file as a string (as opposed to a line by line based approach) and then do a gsub where I increment/decrement all the time strings. To be able to do arithmetic on the time values I first convert them into milliseconds and then format them to the desired format. Everything else is pretty minimal, there is no error handling or parameter checking for example, but it follows the spec.

Reply

AkitaOnRails October 5, 2009 at 1:00 pm

The code worked in the first shot with my test file. Very straight forward, though not the best solution, but workable.

Reply

Michael Kohl October 5, 2009 at 1:01 pm

I like the use of constants and the idiomatically named to_ms method.

Reply

ashbb October 5, 2009 at 1:07 pm

I ran the code. It worked well. I got no errors and got correct output. The code is simple, easy to read and has no tricks.

Reply

Robison WR Santos September 27, 2009 at 8:53 pm

Solution (#5):

[Description]: Well, I’ve assumed the simple approach to get the solution. Actually what I did can be divided into some tasks:

  • Read the input file.
  • Find the timestamp in the file.
  • Transforms it in seconds.
  • Make the transformation.
  • Write back in the output file.

[Country]: Brazil

[Code]: https://gist.github.com/92e2a8426d44a3d465ab

[Code Works]: Only tested in Ruby 1.8

Reply

ashbb October 5, 2009 at 1:10 pm

I think there is no need to use exit! instead of exit in this case.

Reply

Michael Kohl October 5, 2009 at 1:11 pm

Nice layout, includes tests, fairly idiomatic Ruby. Not bad at all!

Reply

AkitaOnRails October 5, 2009 at 1:12 pm

He worried about overall project structure, code readability, documentation, tests. But the algorithm itself is around the same idea as everybody else and not exactly the simplest.

Reply

Aldric September 27, 2009 at 10:27 pm

Solution (#6):

Country of Residence: USA

Code works with Ruby 1.8 (don’t know enough to handle UTF-8 SRTs yet).

Code: https://gist.github.com/c60e13f3f43f22823f73

Reply

AkitaOnRails October 5, 2009 at 2:35 pm

Overall he does the same thing as everybody else: convert the string to milliseconds, make the operation, format back to string. He also reads the entire file to memory instead of streaming it. It’s not bad though.

Reply

Michael Kohl October 5, 2009 at 2:36 pm

I like this solution. Good use of option parser, idiomatic Ruby. Just the whole could be improved upon.

Reply

Rodrigo Rosenfeld Rosas September 28, 2009 at 12:14 am

Solution (#7):

Submitted for fun.

I’m from Brazil.

Here is my contribution: (actually I don’t expect any prizes, but it seemed fun to write such a solution :) )

I really didn’t like the optparse from standard lib because of its bad documentation and lack of some features. When I first read the challenge, I thought I should implement the option parser, and I did it much faster than it took to learn how to use optparse appropriately, but here is the solution that uses optparse:

Code: https://gist.github.com/19b52b0d3aa6441dddda

This code was tested with 1.8, but I believe it should work also on 1.9.

Congratulations to RubyLearning for this initiative :)

Reply

Rodrigo Rosenfeld Rosas October 6, 2009 at 8:36 am

I’m posting a new url with correct formating, since the code in the above link is not understandable:

http://gist.github.com/202717

Reply

HugoLnx September 28, 2009 at 5:55 am

Thanks, now I understand. ^^
“So if you want to add 1 minute you type ruby shift_subtitle.rb –operation add –time 1,000,”
I thought to add 1 minute would be …
–operation add-time 60,0
Thanks for the clarification.xD

Reply

Akshay Gupta September 28, 2009 at 1:52 pm

Solution (#8):

From India.

Works on Both 1.8 and 1.9

Solution: https://gist.github.com/b0822d661c735cd3c161

Explanation :
Reads the input_file from STDIN, maps all time pieces with the new time and writes it to the output_file.

Test cases :
* Input_File *
645
01:32:04,283 –> 01:32:07,769
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,

# ruby srt_convert.rb –operation add –time 11,222 Input_file.srt Output_file.srt

* Output_File *
645
01:32:15,505 –> 01:32:18,991
the government is implementing a new policy…

646
01:32:06,150 –> 01:32:08,886
In connection with a dramatic increase
in crime in certain neighbourhoods,

http://github.com/kitallis/RPCFN/tree/master/1/

Reply

AkitaOnRails October 5, 2009 at 2:39 pm

If I have the time “00:00:00,574″, and I add “2,500″, I should have “00:00:03,074″, right? His algorithm gives me “00:00:03,740″, which I think is not correct. This happens because the Time#usec seems to be misleading, sometimes it gives back a 6 digit number, sometimes it gives back less digits. This guy thought it through, this is different than the others, but he lacks on readability and maintainability.

Reply

Fabio Kreusch September 28, 2009 at 6:34 pm

Solution (#9):

Country: Brazil
My Solution: http://github.com/fabiokr/ruby_learning_challenge_1
Code developed with a Ruby 1.8 environment

Reply

AkitaOnRails October 5, 2009 at 2:42 pm

For me it’s giving me an error right off the bat:

$ ruby shift_subtitle.rb –operation add –time 2,500 input.srt output.srt
./lib/subtitle.rb:51:in `separate_into_blocks’: undefined method `text’ for nil:NilClass (NoMethodError)
from ./lib/subtitle.rb:39:in `each’
from ./lib/subtitle.rb:39:in `separate_into_blocks’
from ./lib/subtitle.rb:34:in `load_file_data’
from ./lib/subtitle.rb:4:in `initialize’
from shift_subtitle.rb:45:in `new’
from shift_subtitle.rb:45

So, it never actually finishes. It has a lot of unnecessary clutter, specially in the ‘blocks’ thing (which is what is giving me errors).

Reply

Chris Jones September 28, 2009 at 11:48 pm

Solution (#10):

From USA
Solution: http://gist.github.com/195637
Only tested with Ruby 1.8

Reply

AkitaOnRails October 5, 2009 at 2:47 pm

For me it’s giving a weird result, for example, if I have this input: “00:00:06,673″, and I add 2,500, it’s returning me “12:00:09,173″. If I ignore that, it has the same problem I described earlier about the usec padding: for example “00:09:49,563″ plus 2,5 is “00:09:52,063″ but it is giving me “00:09:52,630″, which is wrong. Again, this the correct direction, but there is this small usec treatment.

Reply

ashbb October 5, 2009 at 2:50 pm

Personally, I like using open() method with block and using if statement like this:

if OPTIONS[:operation] || OPTIONS[:time]

instead of:

if OPTIONS[:operation] == nil || OPTIONS[:time] == nil

Reply

dominikh September 29, 2009 at 12:46 am

Solution (#11):

Submitted for fun

Brief description: Basically using String#scan with an appropriate
Regexp to create instances of entries. Offset is being saved using
instances of Time (and taking care of converting microseconds to
milliseconds)

Country of Residence: Germany

Special note: I am truly no newbie anymore, so I shouldn’t be taken
into account when choosing the winner. I just wanted to participate
for the fun of it, and maybe the code helps some of the others.

My Solution: https://gist.github.com/4234c8580a166428e5da

Reply

Charles Feduke September 29, 2009 at 12:51 am

I began on Friday not really knowing much of anything about Ruby (other than some very minor Rails tutorials). I have almost completed this and have so far learned a lot. Definitely the right amount of challenge for me and worth spending the time on.

Reply

Milan Dobrota September 29, 2009 at 3:11 am

ruby shift_subtitle.rb –operation add –time 1,000 is supposed to add 1 second and not one minute to the time range.
Btw, what is the best way to test optparser?

On a different note: It would be a good idea if you could organize Rails competitions as well. The students could build different Rails plugins, while reading and learning Rails source code.

Reply

Charles Feduke September 29, 2009 at 11:17 am

To test optparser and my CLI arguments I used RSpec and wrote tests like “should only accept the appropriate #,### format for time”. You can either throw an exception when something is incorrect, then expect it in your test, or immediately exit. I went for the latter, but testing exit can be tricky. I figured out an easy way to mock Kernel and posted it up on my blog (http://www.deploymentzone.com/2009/09/26/ruby-mocking-kernel-exit/).

Is it the best way? I dunno, this is my first foray into Ruby so probably not. But it works so I guess that’s something.

Reply

Chuck Ha September 29, 2009 at 6:48 am

Solution (#12):

Country of Residence: USA
My Solution (i.e. Ruby code): https://gist.github.com/f776754f514f54bbeb26
Code works with Ruby 1.8 and 1.9

Reply

Michael Kohl October 5, 2009 at 2:55 pm

A bit too complicated and repetitive for my personal taste, but definitely not bad for a Python coder coming over to Ruby-land.

Reply

AkitaOnRails October 5, 2009 at 2:56 pm

Worked right off the bat, correct results, but it was just too brute force for my taste.

Reply

ashbb October 6, 2009 at 6:13 am

Ran the code with Ruby 1.9.1 and got the output file well. It was converted correctly.
With Ruby 1.8.6, need to treat the message – undefined method ‘each_char’

Reply

Milan Dobrota September 30, 2009 at 2:08 am

Solution (#13):
Country of Residence: Serbia
My Solution: http://github.com/milandobrota/shiftsubtitle
Code Tested with ruby 1.8.6
Explanation: Custom OptionParser using optparse for parsing CL arguments + Shifter for actual time shifting and reading/writing to a SRT file.
Test cases: included with the link

Reply

AkitaOnRails October 5, 2009 at 2:57 pm

Finally, the first one to actually do the gem and rspec tests. But, it lacked the last mile. He didn’t know about the ‘executable’ property in the gemspec so he did not tailor a proper ‘bin’ executable. The project lacks a ‘Rakefile’ so I could just run ‘rake spec’, and one of the spec files fails though. Again, I am having weird results. It feels like my environment is wrong or something, because I got everything shifted by 20 hours (min, sec, msec were correct though). I don’t have time to investigate, so I am assuming I am doing something wrong. He got the millisecond operations correctly.

Reply

Parag Shah October 1, 2009 at 1:08 am

Solution (#14):

Country of Residence: India
My Solution: https://gist.github.com/556d5864579da6bb60d7
Code works with Ruby 1.8.6

Reply

AkitaOnRails October 5, 2009 at 2:59 pm

Feels like this code is incomplete as it tries to use stuff not declared anywhere such as ‘output_file’ and ‘ShiftSubtitle’, I don’t think it will run. The code is poorly indented. Feels like a C programmer taking care of lots of stuff such as stripping strings, taking care of numeric boundaries, but he missed the point.

Reply

Michael Kohl October 5, 2009 at 3:00 pm

Doesn’t use optparse and all and is rather verbose…

Reply

Mike Hodgson October 1, 2009 at 1:47 am

Solution (#15):

Submitted for fun
Code: https://gist.github.com/52cba60961f3723fddf3

Reply

HugoLnx October 1, 2009 at 4:52 am

Solution (#16):

Name: Hugo Roque de Figueiredo
Country of Residence: Brazil
Code works with Ruby 1.8
Explanation: http://www.4shared.com/file/136641094/630bdb62/SubTitle_Handler_Documentation_v0010.html
Solution: https://gist.github.com/89220c46effa04a01b95

Reply

Michael Kohl October 5, 2009 at 3:01 pm

A bit too long and complicated for my taste…

Reply

AkitaOnRails October 5, 2009 at 3:02 pm

I think this is the most complicated variation of the “convert2msec/calculate/format2string” formula so far :-) But it fails to give me the correct results. Actually, it converts just the first time range of the file and ignores all the rest.

Reply

HugoLnx October 1, 2009 at 4:57 am

I will send my solution today, but I don’t see the message ‘waiting for a moderator’ or something like that. I just want to be sure that my solution is received.xD

Reply

Satish Talim October 1, 2009 at 11:09 am

Your solution is there. Don’t worry.

Reply

PhilK October 2, 2009 at 1:16 am

Does the “no external gems” restriction also apply to packaging the gem or just the strict functionality? (i.e. Can I use jeweler to create my gem?)

Reply

AkitaOnRails October 2, 2009 at 8:11 am

Yes, for gem creation it is ok to use external help, though I would argue that it will only add bloat to this small project :-)

Reply

PhilK October 2, 2009 at 7:33 pm

Any good links for manual gem packaging? I’ve always just followed the tools like jeweler and newgem because I didn’t know how to do it myself.

Reply

Michael Kohl October 3, 2009 at 2:50 pm
John McDonald October 2, 2009 at 2:16 am

Solution (#17):

Country: USA
Code works with Ruby 1.8 / 1.9 / : yes!
Explanation: Created as a gem (using windows). Command line arguments are examined for correct usage and program exits with an error if it receives invalid values.
Test cases: didn’t get time to do this.
Code: https://gist.github.com/d900ab61c47bdcd0edc7

Reply

AkitaOnRails October 5, 2009 at 3:04 pm

For me, it’s giving this error:

$ ruby shift_subtitle.rb –operation add –time 2,500 input output
shift_subtitle.rb:78:in `update_srt’: ’10′ is not a valid sequence number (RuntimeError)
from shift_subtitle.rb:140

Seems like he is not doing the correct regex. I changed line 74 for “if line =~ /^[0-9]+$/”. Then it runs, but the results are not ok. He is having the msec issue. For instance, his results are like “00:09:52,63″ when it should’ve been “00:09:52,063″. He knows about gem executable, which is good. Code is following the labor intensive formula again.

Reply

Felipe Elias Philipp October 2, 2009 at 8:25 am

Solution (#18) – Winner:

Country: Brazil
My Solution: http://github.com/felipeelias/shift_subtitle
Code works with Ruby 1.8

Reply

AkitaOnRails October 5, 2009 at 3:05 pm

Very Nice! This is the best by a large margin so far. He correctly followed the shorter algorithm using ‘Time’, he was aware of the msec issue and correctly did the ‘sprintf’ trick. So this is the first to pass my entire 3,000 lines test file perfectly. He also worried about readability, gem building, but testing was a bit lacking.

Reply

Michael Kohl October 5, 2009 at 3:06 pm

Nice, including tests and gemspec.

Reply

Charles Feduke October 2, 2009 at 9:42 am

Solution (#19):

USA
Code tested with Ruby 1.8.6 on Windows (probably works with 1.9)
First real attempt at non-Rails Ruby using RSpec and unit tests… I had to use the ‘construct’ gem for my unit tests because I don’t think I could write a file system mocking framework in Ruby… yet. The actual program requires nothing external to what is provided with Ruby 1.8.6.
Solution: https://gist.github.com/22f423715334c756b3aa

Reply

Michael Kohl October 5, 2009 at 3:08 pm

A very complicated way to deal with subtraction. In my example solution I do this:

options[:time] *= -1 if options[:operation] == :subtract

so I can always just add (adding a negative number == subtracting).

Overall this is a pretty nice solution though! :)

Reply

AkitaOnRails October 5, 2009 at 3:09 pm

Almost there, so close. He did the usec / 1_000 but forgot the sprintf(“%.3d”) trick to round it up. So he gives me results such as “00:03:16,74″ instead of “00:03:16,074″. The overall structure is not bad. I really expected people to modularize it more though. No gem building, some tests (couldn’t make them run), readable code overall.

Reply

PhilK October 2, 2009 at 9:13 pm

Ok, last question. Any advice for dealing with the beginning when you’re removing time? I’m having trouble wrapping my head around what I should do when I come up with negative times at the beginning of the file. I could just drop them if they’re negative values but then I’ll either lose those subtitles or I’ll not have times to put on subtitles at the end. I had a pretty nice setup till I realized this. I don’t really think I’m capable of implementing a time_travel method so the computer can display subtitles before you open the video :)

Reply

Michael Kohl October 3, 2009 at 5:29 pm

Assuming that a lot of movies don’t actually have dialog from the very first second and subtitle shifts are usually small, I’d probably ignore it for this exercise.

Reply

Hari Rajagopal October 3, 2009 at 10:43 am

Solution (#20):

Country of Residence: USA
Solution: http://github.com/hariis/RPCFN–Shift-Subtitle—1-
Tested with 1.8.6

Reply

AkitaOnRails October 5, 2009 at 3:10 pm

The program is incorrect. For instance, I am getting results such as “00:11:60,099″, instead of “00:12:00,099″. He is missing boundary conditions on seconds (00:60 should round up to 01:00). No gem building, no tests, readable enough, poorly indented, still following the more brute force approach.

Reply

Brad O'Connor October 3, 2009 at 11:12 am

Solution (#21):

From Australia
Solution: https://gist.github.com/20b7ea938d7923944795
Code works with Ruby 1.9. Should also work with Ruby 1.8 but I only have 1.9 installed
This is a bare minimum attempt at the challenge. It will fail very inelegantly if there is anything wrong with the arguments. I also set it to never give a time less than 00:00:00,000 because that would obviously be pointless. I’m sure there is a much more elegant way of doing this and I am looking forward to seeing others’ solutions.
No test cases.

Reply

AkitaOnRails October 5, 2009 at 3:11 pm

Couldn’t make it run. Didn’t stop to debug it, due to lack of time.

Reply

Michael Kohl October 5, 2009 at 3:12 pm

I like this for its simple and straightforward approach but I could not run the program.

Reply

Oliver October 3, 2009 at 6:56 pm

Solution (#22):

I’m from the UK.

Source: http://github.com/olivernn/ShiftSubtitle

You can install my solution to the problem from gemcutter using:
gem install shift_subtitle.

I chose to package my script as a gem containing an executable file, once it is installed simply run shift_subtitle -h for the options to pass to it.

I decided to add a start position to my script so that if the subtitles are not quite synced properly then they can be shifted from a particular point.

I have included some unit tests for the classes used in the application too. I have tested this application with Ruby 1.8.6 only.

Reply

AkitaOnRails October 5, 2009 at 3:13 pm

Almost there. He did a good job on maintainability, project organization, gem building. But, something went wrong. I followed the readme and it did run without any exceptions thrown or anything, but my output.srt came out blank.

Reply

Oliver October 5, 2009 at 10:39 pm

Bah! I was having this problem before – it is because I originally had a regex that was checking that the input file was indeed a .srt file, this didn’t work so I removed it at the last minute, I must have forgotten to update the gem on gemcutter though. The correct version should be up there now.

Next time I will be more thorough with the versioning!

Reply

Jacob October 4, 2009 at 3:37 am

Solution (#23):

Reply

AkitaOnRails October 5, 2009 at 3:15 pm

Not bad, the algorithm is in the right direction. But lacked on project structure. He also fell for the msec issue, giving me “00:03:16,74″ instead of “00:03:16,074″.

Reply

Jacob October 5, 2009 at 8:57 pm

Hey Akita, thanks for taking the time to go over my code. This is such a great way to learn, so thanks everybody who is responsible for making this happen!

Regarding my solution … I briefly skimmed the other results and I think the msec issue is in there somewhere, so I’ll find that, but could you elaborate on proper project structure a little bit and some of the things that I could’ve done better in this area?

ps
No time to write tests or package as a gem, but definitely plan on looking into those things as soon as I can!

Again, thanks everybody, these are so great and I’m looking forward to the next challenge!

Reply

AkitaOnRails October 6, 2009 at 8:55 pm

Well, as I said in the general comments below (http://rubylearning.com/blog/2009/09/24/rpcfn-shift-subtitle-1/comment-page-1/#comment-119436), as this exercise is actually small, having it all in one file may not be considered bad, but it’s always good to have in mind separating concerns. For instance, the smallest solution would be to have at least 2 files: one for the command line optparse stuff and another for the algorithm. Though that would lead you to want to wrap it up in a gem :-)

Your code is particularly well written, no obscure namings, no giant one-liners. It is good as it is. Maybe just one small thing is the accumulating everything in one big string and then writing it down at once to a file at the end. Check out my first general comment below about file streaming.

Just as a comment for the exercise itself, I am being a little picky :-)

Reply

Todd Huss October 4, 2009 at 10:05 am

Solution (#24):

Country of Residence: USA
Solution: http://github.com/thuss/shift_subtitle
Gem: http://gemcutter.org/gems/thuss-shift_subtitle
Code works with both Ruby 1.8 and 1.9
Explanation: I opted to create the Gem and go for 100% RSpec coverage including testing bin/shift_subtitle script. The gem itself requires no dependencies to use, but running the specs requires rspec and jeweler.

Reply

ashbb October 5, 2009 at 3:17 pm

shift_subtitle –operation add –time 1,234 input.srt output.srt

didn’t work. Got an error:

shift_subtitle_cli.rb:45:in `pop’: wrong number of arguments (1 for 0) (ArgumentError)

I edited a bit like this:

arguments.pop(2)` –> `arguments[0], arguments[1]
It then worked well.

Reply

AkitaOnRails October 5, 2009 at 3:18 pm

He went through the right path in terms of algorithm. Project structure is good. Gem build is ok. Rspec test support is good. I did have to use Satoshi’s (ashbb) fix, then it worked and was able to go through my entire test file flawlessly.

Reply

Todd Huss October 6, 2009 at 8:37 am

I’ve pushed a new version of the gem with this fix. So apparently the pop(n) method was added in Ruby 1.8.7 (which my new Macbook came with) but is not in Ruby 1.8.6. I even checked the API docs beforehand to make sure it was in both Ruby 1.8.7 and 1.9, but it never occurred to me to check if it was in Ruby 1.8.6:

http://apidock.com/ruby/Array/pop

Hopefully that doesn’t disqualify me!

Reply

ashbb October 6, 2009 at 3:31 pm

Hi Todd,

Oh, sorry. It’s my fault. I’ve installed ruby 1.8.6 and 1.9.1 in my pc.
Now I confirmed with ruby 1.9.1. Your solution works well. No errors. :-D

> Hopefully that doesn’t disqualify me!
hahaha. Don’t worry. Your solution is great. :-)

D:\work>ruby -v
ruby 1.9.1p243 (2009-07-16 revision 24175) [i386-mswin32]

D:\work>irb
irb(main):001:0> a = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> a.pop(2)
=> [4, 5]

Reply

Antonio October 4, 2009 at 12:27 pm

Solution (#25):

  • Name: Antonio
  • Country of Residence: Canada
  • Solution: http://github.com/kainage/shift_subtitle
  • Code works with Ruby 1.8 and 1.9: Hopefully.
  • Explanation: Nothing but what the quiz asked for, with some simple error checking/handling and a small verbose mode.
  • Test cases: None, did not have the time.

Reply

AkitaOnRails October 5, 2009 at 3:21 pm

Nice, worked perfectly right off the bat with the entirety of my test file, including the dreaded msec issue. It’s simple, readable enough. Lacks on project structure.

Reply

Sriram Varahan October 4, 2009 at 7:25 pm

Solution (#26):

Country: India

Solution: https://gist.github.com/26362d5f62ecc8351a16
Code works with: Ruby 1.8

Reply

AkitaOnRails October 5, 2009 at 7:10 pm

The script worked right off the bat, passed through my test file. He did the 1st style of milliseconds convert and re-format. Not bad. Well written code overall.

Reply

Sriram Varahan October 6, 2009 at 9:19 pm

Thanks for taking your time to review the solution.
Thanks to the Ruby Learning team for organizing such a challenge. I got to learn a lot in the process. Looking forward to the next challenge …

Reply

giordano scalzo October 5, 2009 at 1:11 am

Solution (#27):

Your name: Giordano Scalzo
Country of Residence: Italy
My code is on
http://github.com/gscalzo/ShiftSubtitle
Code tried with 1.8, but it should work with 1.9 too
Test cases: the code was written in TDD using Rspec, in repository you will find the specs to.

I created a gem specification too, shift_subtitle.gemspec.

I learned a lot, I’m quite satisfied with the result, but I would have clean it a little more… I’m looking forward to next quiz!

Reply

AkitaOnRails October 5, 2009 at 3:28 pm

Project with good structure. a little bit of over-engineering having lib/add.rb and lib/sub.rb feels very unnecessary. Gem building works. Reading the code feels like it is going to work or come close, but I had this issue:

$ ruby bin/shift_subtitle –operation add –time 2,500 ../input.srt output.srt

Shifting with following parameters:

Operation: [add]
Time: [2,500]
Input file: [../input.srt]
Output file: [output.srt]
./bin/../lib/../lib/../lib/operation.rb:20:in `as_time’: uninitialized constant Operation::Date (NameError)
from ./bin/../lib/../lib/add.rb:7:in `operation’
from ./bin/../lib/../lib/../lib/operation.rb:7:in `on’
from ./bin/../lib/../lib/operation_performer.rb:25:in `evaluate’
from ./bin/../lib/../lib/operation_performer.rb:14:in `perform’
from ./bin/../lib/../lib/operation_performer.rb:8:in `on’
from ./bin/../lib/../lib/operation_performer.rb:7:in `map’
from ./bin/../lib/../lib/operation_performer.rb:7:in `on’
from ./bin/../lib/shift_subtitle.rb:64:in `processed_lines’
from ./bin/../lib/shift_subtitle.rb:86:in `start’
from bin/shift_subtitle:7

Didn’t look close to debug.

Reply

Giordano Scalzo October 5, 2009 at 5:41 pm

I tried on Windows and on Linux and it worked… on Mac don’t!
It needs
require ‘date’
require ‘time’
in operation.rb

I really have to buy a Mac :-(

Anyway, I had the padding usec bug too… I feel a little dumb ;-)

I pushed my corrections.

Reply

Satish Talim October 5, 2009 at 5:46 pm

Giordano look at this challenge positively. We all have had a great chance to interact with the experts and improve our Ruby further.

Reply

ashbb October 6, 2009 at 6:09 am

With Ruby 1.8.6, gem build/install worked well. Ran shift_subtitle command and got the output file well. It was converted correctly. The code is readable, but 5 classes defined are a bit verbose for my taste. ;-)

Reply

PhilK October 5, 2009 at 1:27 am

Solution (#28):

Might as well register for the contest even though I sure there’s no way I’m going to win it. People on here are super smart. Thanks for the fun challenge BTW. I’ve been looking for a Ruby quiz that wasn’t super easy or super impossible. This one was just right, every time I hit a wall I was able to find a way around it.

USA
http://github.com/philk/subshifter

Code is all on github, hopefully the documentation makes sense and it works correctly for you guys.

Reply

AkitaOnRails October 5, 2009 at 3:37 pm

I was having trouble the lines 42-56 of bin/subshifter so I just took the validations off. This code was able to go through my entire test file correctly! He did it through the brute force approach though, but it’s ok. Gem build is ok, with spec, executables, etc. Good test coverage. Good overall project structure.

Reply

ashbb October 6, 2009 at 6:11 am

Gem build / install worked well. But when I ran the command ‘subshifter –operation add –time 1,234 -i input.srt -c output.srt’, it didn’t work as I expected. Please look at bin/subshfter line 42-46 and 49, 50, 52. Are they in the right place? And one more, please confirm the result with ‘-o remove’ case. ;-)

Reply

AkitaOnRails October 5, 2009 at 12:02 pm

Congratulations for everybody that participated. Just taking your time to think of a solution was good enough. I’ve looked through all of the solutions and I have a few comments:

* Nobody seemed to have had problems on the optparse usage, file manipulation, which is good. Some solutions read the whole file in memory before manipulating it. An Srt file is not big, but just for the sake of it, I would suggest going for the ‘streaming’ pattern, as it’s not more difficult. Something like this:

File.open(output_file, "w") do |output|
  File.readlines(input_file, "r") do |input|
    output.write some_operation(line)
  end
end

* The majority of the solutions were a single file with everything in it. As the solution itself is pretty small maybe it’s not a big problem, but I would recommend structuring your projects a bit more, for instance having a ‘bin’ folder for the executable, ‘lib’ for the libraries, ‘spec’ for the rspec tests and so forth.

* Some of the solutions had gem specs, which is great. But some were not aware that you have the ‘executable’ property. Another good practice maybe to have a rake task to regenerate your spec file to make sure you have the correct version and all the files listed. Github doesn’t allow for I/O scripting within a spec file, so you can’t use “Dir.glob” to dynamically add all your “lib” files, for example, so you have to write it down manually or have a script to regenerate the spec. Look into my linked solution for an example of this approach.

* There were mainly 2 style of solutions for the time shifting itself. The first one was the “parse string time to milliseconds, then calculate, then format back to string”. Most of the solutions went for this approach. This usually leads to the correct answer but is very labor intensive. The second way is to “require ‘time’” and use the Time class. You do Time.parse and then just calculate over it. There is a trick, though, the Time#usec method can be misleading, and when you format back to string you will end up with the wrong answer. Check out my solution in the link at the end of this post for one example. Some people were able to get it correctly in their solutions.

* Finally, code style. I would say that half of the solutions were more “Ruby-like”, some were more “Bash-like” :-) But seriously, I would take more care of indentation, having more understandable naming for methods and variables. This is very important for maintainability.

Overall, most of the solutions were able to get it through. A few of them went all the way to have tests and gem building. Again, congrats for the effort. I hope it motivated some of you to do research and try new things.

Reply

dominikh October 5, 2009 at 4:35 pm

Hm, an idea for the next challenge: Tell everyone to directly use github/gists, saves you from creating the gists yourself. Plus, looks like you actually screwed up: I saw lots of < and " (HTML escape characters, in case it converts them in this comment) in the gists, which probably happened when you simply copy & pasted the comments into gists.

Reply

Satish Talim October 5, 2009 at 4:44 pm

Yes, we all learn with time. I have already changed the same for the #2 challenge onwards -
http://rubylearning.com/blog/ruby-programming-challenge-faq/#rpc5

Hopefully, that would avoid the problems we faced in #1.
Thanks for the suggestion.

Reply

Sriram Varahan October 5, 2009 at 5:00 pm

Hello,
I guess solution #26 was missed. Can you let me know if there was an issue.

Thanks.

Reply

Satish Talim October 5, 2009 at 5:04 pm

Sriram, Fabio will be evaluating your solution asap. Don’t worry.

Reply

Leave a Comment

{ 22 trackbacks }

Previous post:

Next post: