captions 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: df484fcb870ba6563846a9a5f9ba3dd6af72bef1
4
+ data.tar.gz: 4a1ddcd8ac296fc4da482f99c2e34f2f377d1973
5
+ SHA512:
6
+ metadata.gz: f9dba92584da98bb74c7f353b9f33b124d827d89db0df2b707be75f8a4a70b425358bc1fef8c09af112f4e4884c5397916b3d458d8d734ac05401747bc286073
7
+ data.tar.gz: bd37c14625543e6a40743eff374464affd9a154d19a0e680907a43b6c0c658a9789fa043408e3f486772053c56ee829dcda947a04c247ebaa3feac1af8ec668a
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ cache: bundler
3
+
4
+ rvm:
5
+ - 2.0.0-p247
6
+
7
+ script: 'bundle exec rspec'
8
+
9
+ notifications:
10
+ email:
11
+ recipients:
12
+ - navinre93@gmail.com
13
+ on_failure: change
14
+ on_success: never
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in captions.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 navin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # Captions - Subtitle Editor and Converter
2
+
3
+ [![Build Status](https://travis-ci.org/navinre/captions.svg?branch=master)](https://travis-ci.org/navinre/captions)
4
+
5
+ Captions can read subtitles of various formats, modify them and convert the subtitle to other formats. Currently captions supports `SRT`, `WebVTT` files.
6
+
7
+ ## Supported Features
8
+ - Read subtitles
9
+ - Move subtitles
10
+ - Increase duration
11
+ - Filter subtitles
12
+ - Change frame rate
13
+ - Convert to other format (SRT, WebVTT)
14
+
15
+ ## Features Planned
16
+ - Read subtitle properties (Font Style, Aligment, Position etc.)
17
+ - Delete subtitles
18
+ - Change subtitle fonts
19
+ - Command Line Interface
20
+ - Spell Check
21
+ - And More...
22
+
23
+
24
+ ## Usage
25
+
26
+ **Read subtitle file:**
27
+
28
+ ```ruby
29
+ s = Captions::SRT.new('test.srt')
30
+ s.parse
31
+
32
+ s = Captions::VTT.new('test.vtt')
33
+ s.parse
34
+ ```
35
+
36
+ **Filter subtitles:**
37
+
38
+ Once the subtitle file has been parsed. Every subtitle will get **start_time, end_time, duration and text**. All the values are stored in milliseconds. `Captions::InvalidSubtitle` error will be thown if start-time or end-time cannot be found for a subtitle.
39
+
40
+ View all subtitle text:
41
+ ```ruby
42
+ s.cues.map { |c| c.text }
43
+ ```
44
+
45
+ Filter subtitles based on condition:
46
+ ```ruby
47
+ s.cues { |c| c.start_time > 1000 }
48
+ s.cues { |c| c.end_time > 1000 }
49
+ s.cues { |c| c.duration > 1000 }
50
+ ```
51
+
52
+ To get all subtitles:
53
+ ```ruby
54
+ s.cues.each { |c| puts c }
55
+ ```
56
+
57
+ **Move Subtitle:**
58
+
59
+ ```ruby
60
+ s.move_by(1000)
61
+ s.move_by(1000) { |c| c.start_time > 3000 }
62
+ ```
63
+
64
+ Former command moves all subtitles by 1 second. Later moves the subtitles which are starting after 3 seconds by 1 second.
65
+
66
+ **Increase Duration:**
67
+
68
+ ```ruby
69
+ s.increase_duration_by(1000)
70
+ s.increase_duration_by(1000) { |c| c.start_time > 3000 }
71
+ ```
72
+
73
+ Former command increases duration of all subtitles by 1 second. Later increases the duration of subtitles which are starting after 3 seconds by 1 second.
74
+
75
+ **Change Frame Rate:**
76
+
77
+ All subtitles are parsed based on **25 frames/second** by default. This can be changed by passing frame rate at the time of reading the subtitle file.
78
+
79
+ ```ruby
80
+ s = Captions::SRT.new('test.srt', 29.97)
81
+ s.parse
82
+ ```
83
+
84
+ Frame rate can also be changed after parsing. This command changes all the subtitles which are parsed in different frame-rate to new frame-rate.
85
+
86
+ ```ruby
87
+ s.set_frame_rate(29.97)
88
+ ```
89
+
90
+ **Convert to Other Format:**
91
+
92
+ Subtitles parsed in one format can be converted to other format. Currently export is supported for **SRT** and **WebVTT**. Other formats will be added soon.
93
+
94
+ ```ruby
95
+ s = Captions::SRT.new('test.srt')
96
+ s.parse
97
+ s.export_to_vtt('test.vtt')
98
+ ```
99
+
100
+ ## Installation
101
+
102
+ Add this line to your application's Gemfile:
103
+
104
+ ```ruby
105
+ gem 'captions'
106
+ ```
107
+
108
+ Or install it yourself as:
109
+
110
+ $ gem install captions
111
+
112
+
113
+ ## Development
114
+
115
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
116
+
117
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
118
+
119
+ ## Contributing
120
+
121
+ Bug reports and pull requests are welcome on GitHub at https://github.com/navinre/captions.
122
+
123
+
124
+ ## License
125
+
126
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
127
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "captions"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/captions.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'captions/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "captions"
8
+ spec.version = Captions::VERSION
9
+ spec.authors = ["navin"]
10
+ spec.email = ["navin@amagi.com"]
11
+
12
+ spec.summary = %q{Subtitle Editor and Converter written in Ruby}
13
+ spec.description = %q{Subtitle Editor and Converter written in Ruby. Captions can read/modify/export subtitles from one format to another }
14
+ spec.homepage = "https://github.com/navinre/captions"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "bundler", "~> 1.13"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ end
@@ -0,0 +1,162 @@
1
+ module Captions
2
+ class Base
3
+
4
+ # Adding Util methods so that it can be used by
5
+ # any subtitle parser
6
+ include Util
7
+
8
+ # Creates new instance of parser
9
+ # Usage:
10
+ # p = Captions::Base.new(nil, 25)
11
+ #
12
+ # This creates new cue-list with no file object
13
+ # associated with it. `parse` method cannot be
14
+ # called if no file path is specified.
15
+ #
16
+ # p = Captions::Base.new('file_path', 25)
17
+ #
18
+ # This parses the file specified in the file path
19
+ # `parse` method will be defined in any one of the
20
+ # classes which extends this base class. This parses
21
+ # the file in the fps specified. If a subtitle file
22
+ # has to be parsed in different fps, it can be passed
23
+ # as a parameter. FPS parameter plays an important
24
+ # role if the start-time or end-time of a subtitle is
25
+ # mentioned in frames (i.e) HH:MM:SS:FF (frames)
26
+ def initialize(file=nil, fps=25)
27
+ @cue_list = CueList.new(fps)
28
+ @file = File.new(file, 'r:bom|utf-8') if file
29
+ end
30
+
31
+ # This overrides the existing cuelist which has been
32
+ # populated with a new cuelist. This is mainly used in
33
+ # export of one format to another.
34
+ def cues=(cue_list)
35
+ @cue_list = cue_list
36
+ end
37
+
38
+ ############## BEGINNING OF OPERATIONS #################
39
+ ## Following Operations can performed on subtitles
40
+
41
+ # A subtitle is parsed with 25 fps by default. This default
42
+ # value can be changed when creating a new parser class.
43
+ # When the subtitle is being parsed, it takes the value
44
+ # mentioned when the class is created. Even after the
45
+ # subtitle is parsed, frame rate (fps) can be changed using
46
+ # this method.
47
+ # Usage:
48
+ # p = Captions::Base.new('file_path', 25)
49
+ # p.parse
50
+ # p.set_frame_rate(29.97)
51
+ #
52
+ # This method changes all the subtitle which are parsed to
53
+ # the new frame rate.
54
+ def set_frame_rate(rate)
55
+ @cue_list.frame_rate = rate
56
+ end
57
+
58
+ # This returns the subtitle which are parsed. A block
59
+ # can also be passed to filter the cues based on following
60
+ # parameters.
61
+ # Usage:
62
+ # p.cues
63
+ # p.cues { |c| c.start_time > 1000 }
64
+ # p.cues { |c| c.end_time > 1000 }
65
+ # p.cues { |c| c.duration > 1000 }
66
+ #
67
+ # Filters based on the condition and returns new set of cues
68
+ def cues(&block)
69
+ if block_given?
70
+ base = self.class.new()
71
+ base.cues = fetch_result(&block)
72
+ return base.cues
73
+ else
74
+ @cue_list
75
+ end
76
+ end
77
+
78
+ # Moves subtitles by `n` milliseconds
79
+ # Usage:
80
+ # p.move_by(1000)
81
+ # p.move_by("00:00:02.000")
82
+ # p.move_by(1000) { |c| c.start_time > 2000 }
83
+ #
84
+ # This changes start-time and end-time of subtiltes by
85
+ # the time passed.
86
+ def move_by(diff, &block)
87
+ msec = sanitize(diff, frame_rate)
88
+ fetch_result(&block).each do |cue|
89
+ cue.start_time += msec
90
+ cue.end_time += msec
91
+ end
92
+ end
93
+
94
+ # Increases duration of subtitles by `n` milliseconds
95
+ # Usage:
96
+ # p.increase_duration_by(1000)
97
+ # p.increase_duration_by("00:00:02.000")
98
+ # p.increase_duration_by(1000) { |c| c.start_time > 2000 }
99
+ #
100
+ # This increases duration of subtiltes by the time passed.
101
+ def increase_duration_by(diff, &block)
102
+ msec = sanitize(diff, frame_rate)
103
+ fetch_result(&block).each do |cue|
104
+ cue.duration += msec
105
+ end
106
+ end
107
+
108
+ ################ END OF OPERATIONS ###################
109
+
110
+ private
111
+
112
+ # This is the base method through which all subtitle
113
+ # file parsing should be done. This throws error if
114
+ # no `@file` is found. This also closes the file once
115
+ # the file has be parsed. All other logic like when the
116
+ # subtitle has to be inserted into the list, when to set
117
+ # the start and end time will be defined inside `parse`
118
+ # method.
119
+ def base_parser
120
+ raise UnknownFile, "No subtitle file specified" if @file.nil?
121
+ begin
122
+ yield
123
+ ensure
124
+ @file.close
125
+ end
126
+ return true
127
+ end
128
+
129
+ # This is the base method through which all subtitle
130
+ # exports has to be done. When a `file_path` is passed
131
+ # to this base method, it opens the file. Other logic
132
+ # about how the file should be written is defined inside
133
+ # `dump` method in one of the subclass.
134
+ def base_dump(file)
135
+ begin
136
+ File.open(file, 'w') do |file|
137
+ yield(file)
138
+ end
139
+ end
140
+ return true
141
+ end
142
+
143
+ # This returns the frame-rate which was used for parsing
144
+ # the subtitles
145
+ def frame_rate
146
+ @cue_list.fps
147
+ end
148
+
149
+ # This accepts a block and returns the result based
150
+ # on the condition passed. This acts on the cuelist
151
+ # whenever a block is passed it does select with the
152
+ # condition passed and returns the result. When no
153
+ # block is passed, it returns the entire cuelist.
154
+ def fetch_result(&block)
155
+ if block_given?
156
+ @cue_list.select(&block)
157
+ else
158
+ @cue_list
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,66 @@
1
+ module Captions
2
+ class Cue
3
+ include Util
4
+
5
+ attr_accessor :number, :start_time, :end_time, :duration, :text, :property
6
+
7
+ # Creates a new Cue class
8
+ # Each cue denotes a subtitle.
9
+ def initialize
10
+ self.text = nil
11
+ self.start_time = nil
12
+ self.end_time = nil
13
+ self.duration = nil
14
+ self.number = nil
15
+ self.property = {}
16
+ end
17
+
18
+ # Sets the time for the cue. Both start-time and
19
+ # end-time can be passed together. This just assigns
20
+ # the value passed.
21
+ def set_time(start_time, end_time, duration = nil)
22
+ self.start_time = start_time
23
+ self.end_time = end_time
24
+ self.duration = duration
25
+ end
26
+
27
+ # Serializes the values set for the cue.
28
+ # Converts start-time, end-time and duration to milliseconds
29
+ # If duration is not found, it will be calculated based on
30
+ # start-time and end-time.
31
+ def serialize(fps)
32
+ raise InvalidSubtitle, "Subtitle should have start time" if self.start_time.nil?
33
+ raise InvalidSubtitle, "Subtitle shold have end time" if self.end_time.nil?
34
+
35
+ begin
36
+ ms_per_frame = (1000.0 / fps)
37
+ self.start_time = convert_to_msec(self.start_time, ms_per_frame)
38
+ self.end_time = convert_to_msec(self.end_time, ms_per_frame)
39
+ if duration.nil?
40
+ self.duration = self.end_time - self.start_time
41
+ else
42
+ self.duration = convert_to_msec(self.duration, ms_per_frame)
43
+ end
44
+ rescue
45
+ raise InvalidSubtitle, "Cannot calculate start-time or end-time"
46
+ end
47
+ end
48
+
49
+ # Changes start-time, end-time and duration based on new frame-rate
50
+ def change_frame_rate(old_rate, new_rate)
51
+ self.start_time = convert_frame_rate(self.start_time, old_rate, new_rate)
52
+ self.end_time = convert_frame_rate(self.end_time, old_rate, new_rate)
53
+ self.duration = convert_frame_rate(self.duration, old_rate, new_rate)
54
+ end
55
+
56
+ # Adds text. If text is already found, new-line is appended.
57
+ def add_text(text)
58
+ if self.text.nil?
59
+ self.text = text
60
+ else
61
+ self.text += "\n" + text
62
+ end
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,27 @@
1
+ module Captions
2
+
3
+ # Used for all execptions thrown from captions
4
+ class CaptionsError < StandardError
5
+ end
6
+
7
+ # Subtitle has to be in correct format
8
+ # when its being serialized
9
+ class InvalidSubtitle < CaptionsError
10
+ end
11
+
12
+ # Subtitle has to be correct format
13
+ # when its being parsed
14
+ class MalformedString < CaptionsError
15
+ end
16
+
17
+ # File has to be specifed for parsing
18
+ # subtitle
19
+ class UnknownFile < CaptionsError
20
+ end
21
+
22
+ # Input has to be proper for filtering
23
+ # subtitles
24
+ class InvalidInput < CaptionsError
25
+ end
26
+
27
+ end
@@ -0,0 +1,25 @@
1
+ module Captions
2
+
3
+ # This module adds support for export of subtitles
4
+ # from one format to another. Subclasses which
5
+ # extends Captions::Base needs to `dump` method to
6
+ # support export to that corresponding format
7
+ module Export
8
+ Captions.constants.each do |format|
9
+ obj = Captions.const_get(format)
10
+ next unless obj.is_a?(Class) and (obj.superclass == Captions::Base)
11
+ method_name = "export_to_" + format.to_s.downcase
12
+ define_method(method_name) do |file|
13
+ sub_format = obj.new()
14
+ sub_format.cues = self.cues.dup
15
+ sub_format.dump(file) if sub_format.respond_to?(:dump)
16
+ end
17
+ end
18
+ end
19
+
20
+ # Including this module after all the sub-classes of
21
+ # Captions::Base has been defined.
22
+ Captions::Base.send(:include, Export)
23
+
24
+ end
25
+
@@ -0,0 +1,65 @@
1
+ module Captions
2
+ class SRT < Base
3
+
4
+ def parse
5
+ base_parser do
6
+ count = 1
7
+ cue = nil
8
+ while(line = @file.gets) do
9
+ line = line.strip
10
+ if is_number?(line)
11
+ @cue_list.append(cue) if cue
12
+ cue = Cue.new
13
+ cue.number = line.to_i
14
+ elsif is_time?(line)
15
+ s , e = get_time(line)
16
+ cue.set_time(s, e)
17
+ elsif is_text?(line)
18
+ cue.add_text(line)
19
+ elsif !line.empty?
20
+ raise MalformedString, "Invalid format at line #{count}"
21
+ end
22
+ count += 1
23
+ end
24
+ @cue_list.append(cue)
25
+ end
26
+ end
27
+
28
+ def dump(file)
29
+ base_dump(file) do |file|
30
+ @cue_list.each do |cue|
31
+ file.write(cue.number)
32
+ file.write("\n")
33
+ file.write(msec_to_timecode(cue.start_time).gsub!('.' , ','))
34
+ file.write(" --> ")
35
+ file.write(msec_to_timecode(cue.end_time).gsub!('.' , ','))
36
+ file.write("\n")
37
+ file.write(cue.text)
38
+ file.write("\n\n")
39
+ end
40
+ end
41
+ end
42
+
43
+ def get_time(line)
44
+ data = line.split('-->')
45
+ return format_time(data[0]), format_time(data[1])
46
+ end
47
+
48
+ def format_time(text)
49
+ text.strip.gsub(/,/,".")
50
+ end
51
+
52
+ def is_number?(text)
53
+ !!text.match(/^\d+$/)
54
+ end
55
+
56
+ def is_time?(text)
57
+ !!text.match(/^\d{2}:\d{2}:\d{2},\d{3}.*\d{2}:\d{2}:\d{2},\d{3}$/)
58
+ end
59
+
60
+ def is_text?(text)
61
+ !text.empty? and text.is_a?(String)
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,63 @@
1
+ module Captions
2
+ class VTT < Base
3
+
4
+ # Header used for all VTT files
5
+ VTT_HEADER = "WEBVTT"
6
+
7
+ # Parse VTT file and update CueList
8
+ def parse
9
+ base_parser do
10
+ count = 1
11
+ cue_count = 0
12
+ cue = nil
13
+ while(line = @file.gets) do
14
+ line = line.strip
15
+ if line.empty?
16
+ @cue_list.append(cue) if cue
17
+ cue_count += 1
18
+ cue = Cue.new
19
+ cue.number = cue_count
20
+ elsif is_time?(line)
21
+ s, e = get_time(line)
22
+ cue.set_time(s, e)
23
+ elsif is_text?(line)
24
+ cue.add_text(line)
25
+ end
26
+ end
27
+ @cue_list.append(cue)
28
+ end
29
+ end
30
+
31
+ # Export CueList to VTT file
32
+ def dump(file)
33
+ base_dump(file) do |file|
34
+ file.write(VTT_HEADER)
35
+ @cue_list.each do |cue|
36
+ file.write("\n\n")
37
+ file.write(msec_to_timecode(cue.start_time))
38
+ file.write(" --> ")
39
+ file.write(msec_to_timecode(cue.end_time))
40
+ file.write("\n")
41
+ file.write(cue.text)
42
+ end
43
+ end
44
+ end
45
+
46
+ # Timecode format used in VTT file
47
+ def is_time?(text)
48
+ !!text.match(/^\d{2}:\d{2}:\d{2}.\d{3}.*\d{2}:\d{2}:\d{2}.\d{3}/)
49
+ end
50
+
51
+ # Get start-time and end-time
52
+ def get_time(line)
53
+ data = line.split('-->')
54
+ return data[0], data[1]
55
+ end
56
+
57
+ # Check whether if its subtilte text or not
58
+ def is_text?(text)
59
+ !text.empty? and text.is_a?(String) and text != VTT_HEADER
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,42 @@
1
+ module Captions
2
+ class CueList
3
+ include Enumerable
4
+
5
+ # Creates a new CueList
6
+ def initialize(frame_rate)
7
+ @fps = frame_rate
8
+ @list = []
9
+ end
10
+
11
+ # Returns frame-rate of the list
12
+ def fps
13
+ @fps
14
+ end
15
+
16
+ # Hide all cues when inspecting CueList
17
+ # Show only necessary info rather than printing everything
18
+ def inspect
19
+ "#<Captions::CueList:#{object_id} @fps=#{fps} @cues=#{@list.count}>"
20
+ end
21
+
22
+ # Changes the frame rate of CueList
23
+ # This also changes frame rate in already parsed
24
+ # subtitles
25
+ def frame_rate=(rate)
26
+ @list.each { |c| c.change_frame_rate(@fps, rate) }
27
+ @fps = rate
28
+ end
29
+
30
+ # Inserts the subtitle into the CueList
31
+ # Subtitle is serialized before its inserted
32
+ def append(cue)
33
+ cue.serialize(@fps)
34
+ @list << cue
35
+ end
36
+
37
+ # Iterate through CueList
38
+ def each
39
+ @list.each { |c| yield(c) }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,82 @@
1
+ module Captions
2
+ module Util
3
+
4
+ # TC should be HH:MM:SS:FF (frames) or HH:MM:SS.MSC (milliseconds).
5
+ # FF(2 digits) and MSC(3 digits) are optional.
6
+ TIMECODE_REGEX = /^-?([01]\d|2[0-3]):[0-5]\d:[0-5]\d(:\d{2}|\.\d{3})?$/
7
+
8
+ # Currently considering frame rate as 25
9
+ # Converts time-code in HH:MM:SS.MSEC (or) HH:MM:SS:FF
10
+ # to milliseconds.
11
+ def convert_to_msec(tc, ms_per_frame=40)
12
+ msec = 0
13
+ negative_multiplier = 1
14
+ if tc[0] == '-'
15
+ tc = tc[1..-1] # remove -ve sign
16
+ negative_multiplier = -1
17
+ end
18
+ tc_split = tc.split('.')
19
+ time_split = tc_split[0].split(':')
20
+
21
+ if tc_split[1] # msec component exists
22
+ msec = tc_split[1].ljust(3, '0').to_i # pad with trailing 0s to make it 3 digit
23
+ elsif time_split.length == 4 # FF (frame) component exists
24
+ msec = time_split[-1].to_i * ms_per_frame.to_f
25
+ time_split.pop # so that below code can work from last index
26
+ end
27
+
28
+ min = 60
29
+ hour = min * 60
30
+ # Get HH:MM:SS in seconds
31
+ sec = time_split[-1].to_i
32
+ sec += time_split[-2].to_i * min
33
+ sec += time_split[-3].to_i * hour
34
+
35
+ msec += sec * 1000
36
+
37
+ return (negative_multiplier * msec.round) # to be consistent with tc_to_frames which also rounds
38
+ end
39
+
40
+ # Converts milliseconds calculated in one frame-rate to another frame-rate
41
+ def convert_frame_rate(msec, old_fps, new_fps)
42
+ old_ms_per_frame = (1000.0 / old_fps)
43
+ new_ms_per_frame = (1000.0 / new_fps)
44
+ frames = (msec / old_ms_per_frame).round # Number of frames in old fps
45
+ sec = frames / old_fps
46
+ frames = frames % old_fps
47
+ new_frames = sec * new_fps
48
+ new_frames += frames # Number of frames in new fps
49
+ return (new_frames * new_ms_per_frame).round # MSEC in new fps
50
+ end
51
+
52
+ # Converts milliseconds to timecode format
53
+ # Currently returns HH:MM:SS.MSEC
54
+ # Supports upto 60 hours
55
+ def msec_to_timecode(milliseconds)
56
+ seconds = milliseconds / 1000
57
+ msec = milliseconds % 1000
58
+ secs = seconds % 60
59
+
60
+ seconds = seconds / 60
61
+ mins = seconds % 60
62
+
63
+ seconds = seconds / 60
64
+ hours = seconds % 60
65
+
66
+ format("%02d:%02d:%02d.%03d",hours, mins, secs ,msec)
67
+ end
68
+
69
+ # Parses time-code and converts it to milliseconds.
70
+ # If time cannot be converted to milliseconds,
71
+ # it throws InvalidInput Error
72
+ def sanitize(time, frame_rate)
73
+ if time.is_a?(String)
74
+ if TIMECODE_REGEX.match(time)
75
+ time = convert_to_msec(time, frame_rate)
76
+ end
77
+ end
78
+ raise InvalidInput, 'Input should be in Milliseconds or Timecode' unless time.is_a? (Fixnum)
79
+ return time
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,3 @@
1
+ module Captions
2
+ VERSION = "1.0.1"
3
+ end
data/lib/captions.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'captions/version'
2
+ require 'captions/error'
3
+ require 'captions/util'
4
+ require 'captions/base'
5
+ require 'captions/list'
6
+ require 'captions/cue'
7
+ require 'captions/formats/srt'
8
+ require 'captions/formats/vtt'
9
+
10
+ require 'captions/export'
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: captions
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - navin
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-10-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.13'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.13'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: 'Subtitle Editor and Converter written in Ruby. Captions can read/modify/export
56
+ subtitles from one format to another '
57
+ email:
58
+ - navin@amagi.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - bin/console
70
+ - bin/setup
71
+ - captions.gemspec
72
+ - lib/captions.rb
73
+ - lib/captions/base.rb
74
+ - lib/captions/cue.rb
75
+ - lib/captions/error.rb
76
+ - lib/captions/export.rb
77
+ - lib/captions/formats/srt.rb
78
+ - lib/captions/formats/vtt.rb
79
+ - lib/captions/list.rb
80
+ - lib/captions/util.rb
81
+ - lib/captions/version.rb
82
+ homepage: https://github.com/navinre/captions
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.4.8
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Subtitle Editor and Converter written in Ruby
106
+ test_files: []