srt 0.0.5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +2 -0
- data/README.md +39 -15
- data/lib/srt/file.rb +69 -63
- data/lib/srt/line.rb +6 -2
- data/lib/srt/parser.rb +31 -0
- data/lib/srt/version.rb +1 -1
- data/lib/srt.rb +2 -1
- data/spec/file_spec.rb +440 -0
- data/spec/{blackswan-part1.srt → fixtures/blackswan-part1.srt} +1962 -1962
- data/spec/{blackswan-part2.srt → fixtures/blackswan-part2.srt} +1567 -1567
- data/spec/{bsg-s01e01.srt → fixtures/bsg-s01e01.srt} +2708 -2708
- data/spec/fixtures/invalid.srt +4 -0
- data/spec/{wotw-dubious.srt → fixtures/wotw-dubious.srt} +5025 -5025
- data/spec/line_spec.rb +25 -0
- data/spec/parser_spec.rb +38 -0
- data/spec/spec_helper.rb +2 -0
- data/srt.gemspec +2 -0
- metadata +42 -24
- data/spec/srt_spec.rb +0 -361
- /data/spec/{coordinates-dummy.srt → fixtures/coordinates-dummy.srt} +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
Mzk5Mjk2ZGU5NjI2NTZjYWVmN2JkN2E1MTY4ZjJkMmEzZmRmNzA5ZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MTIxY2EyYTczM2Q2NTg1NjZlZGNlNDQ4ZGQ4MDRmZDBmOWE3OTA1Yg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MTQyM2M1Y2I4ZDY5MGJkNTk3MWVkNGJkNzk5ZWI5MTJkYjZkMjBiYzhmMDY0
|
10
|
+
YThlZDQyOWQ5YzRmYjNjODkwYjFmMGM4ZjQ1ZTgwN2QwMTJiOWM3Y2I0Y2M1
|
11
|
+
Njc4NTg5ZjI5ZTNiZTAxZjYzYzQzZjg2YmFjODY5NzUyOTVjOTE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NzIyNzExYzBiMjYzZWMzOGVkZDMyZjhiN2EyNWNmNGZhNThhNzkwMDhmNGRk
|
14
|
+
NGE3ZTMxMjA5YmNlYjJmYmNmZmZmYTgwNDIzOWJkZjA2YzE2YzAyZThlOGY5
|
15
|
+
ZTNhYTBlN2ZiYmFlYmY2ZmM4NzI0ODc5NTg4MmFmNDY5YjE1NTE=
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
# SRT
|
1
|
+
# SRT
|
2
|
+
[](https://travis-ci.org/cpetersen/srt)
|
3
|
+
[](https://codeclimate.com/github/cpetersen/srt)
|
4
|
+
[](https://coveralls.io/r/cpetersen/srt?branch=master)
|
5
|
+
[](http://badge.fury.io/rb/srt)
|
2
6
|
|
3
|
-
SRT stands for SubRip text file format, which is a file for storing subtitles; This is a Ruby library for manipulating SRT files.
|
7
|
+
SRT stands for SubRip text file format, which is a file for storing subtitles; This is a Ruby library for manipulating SRT files.
|
4
8
|
Current functionality includes **parsing**, **appending**, **splitting** and **timeshifting** (constant, progressive and framerate-based).
|
5
9
|
|
6
10
|
## Installation
|
@@ -12,7 +16,7 @@ Add this line to your application's Gemfile:
|
|
12
16
|
And then execute:
|
13
17
|
|
14
18
|
$ bundle
|
15
|
-
|
19
|
+
|
16
20
|
Or install it yourself as:
|
17
21
|
|
18
22
|
$ gem install srt
|
@@ -31,7 +35,7 @@ You can parse an SRT file with the following code:
|
|
31
35
|
Each line exposes the following methods/members:
|
32
36
|
* `sequence` The incrementing subtitle ID (starts at 1)
|
33
37
|
* `text` An **Array** holding one or multiple lines of text.
|
34
|
-
* `start_time` The subtitle start timecode in seconds as a float
|
38
|
+
* `start_time` The subtitle start timecode in seconds as a float
|
35
39
|
* `end_time` The subtitle end timecode in seconds as a float
|
36
40
|
* `time_str` Returns a timecode string of the form `"00:53:35,558 --> 00:53:36,556"`
|
37
41
|
* `display_coordinates` Optional display coordinates of the form `"X1:100 X2:600 Y1:100 Y2:400"`
|
@@ -60,37 +64,56 @@ The method `split` splits your subtitles at one (or more) points and returns an
|
|
60
64
|
By default, the timecodes of the split parts are relatively shifted towards their beginnings (to line up with correspondingly split multi-part video);
|
61
65
|
By additionally passing `:timeshift => false` you can prevent that behaviour and retain the original timecodes for each split part.
|
62
66
|
|
67
|
+
Pass the option `:renumber => false` to prevent the line sequence number from being reset for a segment.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
parts = file.split( :at => "01:09:24,000", :renumber => false ) # Split the file in two at 01:09:24 but do not reset the sequence number on the second part
|
71
|
+
```
|
72
|
+
|
63
73
|
Example options for a multi-split: `{ :at => ["00:19:24,500", "01:32:09,120", ...] }`
|
64
74
|
|
75
|
+
|
76
|
+
Optionally, for multi-splitting, you can pass a ":every" option to split the subtitles at a fixed interval.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
parts = file.split( :every => "00:01:00,000" ) # Split the file every 1 minute
|
80
|
+
```
|
81
|
+
Note that the options :at and :every are mutually exclusive, and :at takes precedence.
|
82
|
+
|
65
83
|
#### Timeshifting
|
66
84
|
|
67
85
|
The method `timeshift` takes a hash and supports three different modes of timecode processing:
|
68
86
|
|
69
|
-
**Constant timeshift**
|
87
|
+
**Constant timeshift**
|
70
88
|
|
71
89
|
```ruby
|
72
|
-
file.timeshift( :all => "-2.5s" ) # Shift all subtitles so they show up 2.5 seconds earlier
|
90
|
+
file.timeshift( :all => "-2.5s" ) # Shift all subtitles so they show up 2.5 seconds earlier
|
73
91
|
```
|
74
92
|
|
75
|
-
Simply pass a hash of the form `:all => "[+|-][amount][h|m|s|
|
76
|
-
Other example options, e.g.: `:all => "+
|
93
|
+
Simply pass a hash of the form `:all => "[+|-][amount][h|m|s|ms]"`
|
94
|
+
Other example options, e.g.: `:all => "+1.34m"`, `:all => "0.15h"`, `:all => "90ms"`
|
77
95
|
|
78
96
|
**Progressive timeshift**
|
79
97
|
|
80
98
|
```ruby
|
81
|
-
file.timeshift({ 1 => "00:02:12,000", 843 => "01:38:06,000" }) # Correct drifting-out-of-sync
|
99
|
+
file.timeshift({ "#1" => "00:02:12,000", "#843" => "01:38:06,000" }) # Correct drifting-out-of-sync
|
82
100
|
```
|
83
101
|
|
84
102
|
This example call would shift the **first subtitle** to `00:02:12`, the **last subtitle** (assuming here that `#843` is the last one in your file) to `01:38:06`, and all the ones before, after, and in between those two reference points seamlessly to their own resulting earlier or later begin times.
|
85
103
|
|
86
|
-
To make this work pass two `
|
104
|
+
To make this work pass two `origin timecode => target timecode` pairs, where the *origin timecodes* can be supplied as:
|
87
105
|
|
88
|
-
* `
|
89
|
-
* `[
|
90
|
-
* `"[
|
91
|
-
* `"[hh]:[mm]:[ss],[mil]" => "[+/-][amount][h|m|s|mil]"`
|
106
|
+
* `float` providing the raw timecode in *seconds*, e.g.: `195.65`
|
107
|
+
* `"[hh]:[mm]:[ss],[ms]"` string, which is a timecode in SRT notation, e.g.: `"00:02:12,000"`
|
108
|
+
* `"#[id]"` string, which references the timecode of the subtitle with the supplied id, e.g.: `"#317"`
|
92
109
|
|
93
|
-
|
110
|
+
... and the *target timecodes* can be supplied as:
|
111
|
+
|
112
|
+
* `float` providing the raw timecode in *seconds*, e.g.: `3211.3`
|
113
|
+
* `"[hh]:[mm]:[ss],[ms]"` string, which is a timecode in SRT notation, e.g.: `"01:01:03,300"`
|
114
|
+
* `"[+/-][amount][h|m|s|ms]"` string, describing the amount by which to shift the origin timecode, e.g.: `"+1.5s"`
|
115
|
+
|
116
|
+
So for example: `{ "00:00:51,400" => "+13s", "01:12:44,320" => "+2.436m" }`
|
94
117
|
|
95
118
|
This method can be used to fix subtitles that are *at different times differently out of sync*,
|
96
119
|
and comes in handy especially if you have no idea what framerate your video or the video for which your subtitles
|
@@ -113,3 +136,4 @@ This is usually only useful if you have some background information about the de
|
|
113
136
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
114
137
|
4. Push to the branch (`git push origin my-new-feature`)
|
115
138
|
5. Create new Pull Request
|
139
|
+
|
data/lib/srt/file.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module SRT
|
2
2
|
class File
|
3
|
-
def self.parse(input)
|
3
|
+
def self.parse(input, options = {})
|
4
|
+
@debug = options.fetch(:debug, false)
|
4
5
|
if input.is_a?(String)
|
5
6
|
parse_string(input)
|
6
7
|
elsif input.is_a?(::File)
|
@@ -15,52 +16,51 @@ module SRT
|
|
15
16
|
end
|
16
17
|
|
17
18
|
def self.parse_string(srt_data)
|
18
|
-
result =
|
19
|
-
line =
|
20
|
-
|
19
|
+
result = new
|
20
|
+
line = Line.new
|
21
|
+
|
21
22
|
split_srt_data(srt_data).each_with_index do |str, index|
|
22
23
|
begin
|
23
24
|
if str.strip.empty?
|
24
25
|
result.lines << line unless line.empty?
|
25
|
-
line =
|
26
|
+
line = Line.new
|
26
27
|
elsif !line.error
|
27
28
|
if line.sequence.nil?
|
28
29
|
line.sequence = str.to_i
|
29
30
|
elsif line.start_time.nil?
|
30
31
|
if mres = str.match(/(?<start_timecode>[^[[:space:]]]+) -+> (?<end_timecode>[^[[:space:]]]+) ?(?<display_coordinates>X1:\d+ X2:\d+ Y1:\d+ Y2:\d+)?/)
|
31
32
|
|
32
|
-
if (line.start_time =
|
33
|
-
line.error = "#{
|
34
|
-
puts line.error
|
33
|
+
if (line.start_time = Parser.timecode(mres["start_timecode"])) == nil
|
34
|
+
line.error = "#{index}, Invalid formatting of start timecode, [#{mres["start_timecode"]}]"
|
35
|
+
$stderr.puts line.error if @debug
|
35
36
|
end
|
36
37
|
|
37
|
-
if (line.end_time =
|
38
|
-
line.error = "#{
|
39
|
-
puts line.error
|
38
|
+
if (line.end_time = Parser.timecode(mres["end_timecode"])) == nil
|
39
|
+
line.error = "#{index}, Invalid formatting of end timecode, [#{mres["end_timecode"]}]"
|
40
|
+
$stderr.puts line.error if @debug
|
40
41
|
end
|
41
42
|
|
42
43
|
if mres["display_coordinates"]
|
43
44
|
line.display_coordinates = mres["display_coordinates"]
|
44
45
|
end
|
45
46
|
else
|
46
|
-
line.error = "#{
|
47
|
-
puts line.error
|
47
|
+
line.error = "#{index}, Invalid Time Line formatting, [#{str}]"
|
48
|
+
$stderr.puts line.error if @debug
|
48
49
|
end
|
49
50
|
else
|
50
51
|
line.text << str.strip
|
51
52
|
end
|
52
|
-
|
53
53
|
end
|
54
54
|
rescue
|
55
55
|
line.error = "#{index}, General Error, [#{str}]"
|
56
|
-
puts line.error
|
56
|
+
$stderr.puts line.error if @debug
|
57
57
|
end
|
58
58
|
end
|
59
59
|
result
|
60
60
|
end
|
61
61
|
|
62
|
-
# Ruby often gets the wrong encoding for a file and will throw
|
63
|
-
# errors on `split` for invalid byte sequences. This chain of
|
62
|
+
# Ruby often gets the wrong encoding for a file and will throw
|
63
|
+
# errors on `split` for invalid byte sequences. This chain of
|
64
64
|
# fallback encodings lets us get something that works.
|
65
65
|
def self.split_srt_data(srt_data)
|
66
66
|
begin
|
@@ -75,8 +75,8 @@ module SRT
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def append(options)
|
78
|
-
if options.length == 1 && options.values[0].class ==
|
79
|
-
reshift =
|
78
|
+
if options.length == 1 && options.values[0].class == self.class
|
79
|
+
reshift = Parser.timecode(options.keys[0]) || (lines.last.end_time + Parser.timespan(options.keys[0]))
|
80
80
|
renumber = lines.last.sequence
|
81
81
|
|
82
82
|
options.values[0].lines.each do |line|
|
@@ -91,10 +91,20 @@ module SRT
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def split(options)
|
94
|
-
options = { :timeshift => true }.merge(options)
|
95
|
-
|
96
|
-
|
97
|
-
|
94
|
+
options = { :timeshift => true, :renumber => true }.merge(options)
|
95
|
+
|
96
|
+
split_points = []
|
97
|
+
|
98
|
+
if (options[:at])
|
99
|
+
split_points = [options[:at]].flatten.map{ |timecode| Parser.timecode(timecode) }.sort
|
100
|
+
elsif (options[:every])
|
101
|
+
interval = Parser.timecode(options[:every])
|
102
|
+
max = lines.last.end_time
|
103
|
+
(interval..max).step(interval){ |t| split_points << t }
|
104
|
+
end
|
105
|
+
|
106
|
+
if (split_points.count > 0)
|
107
|
+
split_offsprings = [File.new]
|
98
108
|
|
99
109
|
reshift = 0
|
100
110
|
renumber = 0
|
@@ -102,7 +112,7 @@ module SRT
|
|
102
112
|
lines.each do |line|
|
103
113
|
if split_points.empty? || line.end_time <= split_points.first
|
104
114
|
cloned_line = line.clone
|
105
|
-
cloned_line.sequence -= renumber
|
115
|
+
cloned_line.sequence -= renumber if options[:renumber]
|
106
116
|
if options[:timeshift]
|
107
117
|
cloned_line.start_time -= reshift
|
108
118
|
cloned_line.end_time -= reshift
|
@@ -110,7 +120,7 @@ module SRT
|
|
110
120
|
split_offsprings.last.lines << cloned_line
|
111
121
|
elsif line.start_time < split_points.first
|
112
122
|
cloned_line = line.clone
|
113
|
-
cloned_line.sequence -= renumber
|
123
|
+
cloned_line.sequence -= renumber if options[:renumber]
|
114
124
|
if options[:timeshift]
|
115
125
|
cloned_line.start_time -= reshift
|
116
126
|
cloned_line.end_time = split_points.first - reshift
|
@@ -121,9 +131,9 @@ module SRT
|
|
121
131
|
reshift = split_points.first
|
122
132
|
split_points.delete_at(0)
|
123
133
|
|
124
|
-
split_offsprings <<
|
134
|
+
split_offsprings << File.new
|
125
135
|
cloned_line = line.clone
|
126
|
-
cloned_line.sequence -= renumber
|
136
|
+
cloned_line.sequence -= renumber if options[:renumber]
|
127
137
|
if options[:timeshift]
|
128
138
|
cloned_line.start_time = 0
|
129
139
|
cloned_line.end_time -= reshift
|
@@ -134,9 +144,9 @@ module SRT
|
|
134
144
|
reshift = split_points.first
|
135
145
|
split_points.delete_at(0)
|
136
146
|
|
137
|
-
split_offsprings <<
|
147
|
+
split_offsprings << File.new
|
138
148
|
cloned_line = line.clone
|
139
|
-
cloned_line.sequence -= renumber
|
149
|
+
cloned_line.sequence -= renumber if options[:renumber]
|
140
150
|
if options[:timeshift]
|
141
151
|
cloned_line.start_time -= reshift
|
142
152
|
cloned_line.end_time -= reshift
|
@@ -151,12 +161,12 @@ module SRT
|
|
151
161
|
|
152
162
|
def timeshift(options)
|
153
163
|
if options.length == 1
|
154
|
-
if options[:all] && (seconds =
|
164
|
+
if options[:all] && (seconds = Parser.timespan(options[:all]))
|
155
165
|
lines.each do |line|
|
156
166
|
line.start_time += seconds
|
157
167
|
line.end_time += seconds
|
158
168
|
end
|
159
|
-
elsif (original_framerate =
|
169
|
+
elsif (original_framerate = Parser.framerate(options.keys[0])) && (target_framerate = Parser.framerate(options.values[0]))
|
160
170
|
ratio = target_framerate / original_framerate
|
161
171
|
lines.each do |line|
|
162
172
|
line.start_time *= ratio
|
@@ -164,13 +174,24 @@ module SRT
|
|
164
174
|
end
|
165
175
|
end
|
166
176
|
elsif options.length == 2
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
177
|
+
origins, targets = options.keys, options.values
|
178
|
+
|
179
|
+
[0,1].each do |i|
|
180
|
+
if origins[i].is_a?(String) && Parser.id(origins[i])
|
181
|
+
origins[i] = lines[Parser.id(origins[i]) - 1].start_time
|
182
|
+
elsif origins[i].is_a?(String) && Parser.timecode(origins[i])
|
183
|
+
origins[i] = Parser.timecode(origins[i])
|
184
|
+
end
|
185
|
+
|
186
|
+
if targets[i].is_a?(String) && Parser.timecode(targets[i])
|
187
|
+
targets[i] = Parser.timecode(targets[i])
|
188
|
+
elsif targets[i].is_a?(String) && Parser.timespan(targets[i])
|
189
|
+
targets[i] = origins[i] + Parser.timespan(targets[i])
|
190
|
+
end
|
191
|
+
end
|
171
192
|
|
172
|
-
time_rescale_factor = (
|
173
|
-
time_rebase_shift =
|
193
|
+
time_rescale_factor = (targets[1] - targets[0]) / (origins[1] - origins[0])
|
194
|
+
time_rebase_shift = targets[0] - origins[0] * time_rescale_factor
|
174
195
|
|
175
196
|
lines.each do |line|
|
176
197
|
line.start_time = line.start_time * time_rescale_factor + time_rebase_shift
|
@@ -187,8 +208,17 @@ module SRT
|
|
187
208
|
end
|
188
209
|
end
|
189
210
|
|
190
|
-
def to_s
|
191
|
-
lines.map { |l| [l.sequence, (l.display_coordinates ? l.
|
211
|
+
def to_s(time_str_function=:time_str)
|
212
|
+
lines.map { |l| [l.sequence, (l.display_coordinates ? l.send(time_str_function) + l.display_coordinates : l.send(time_str_function)), l.text, ""] }.flatten.join("\n")
|
213
|
+
end
|
214
|
+
|
215
|
+
def to_webvtt
|
216
|
+
header = <<eos
|
217
|
+
WEBVTT
|
218
|
+
X-TIMESTAMP-MAP=MPEGTS:0,LOCAL:00:00:00.000
|
219
|
+
|
220
|
+
eos
|
221
|
+
header + to_s(:webvtt_time_str)
|
192
222
|
end
|
193
223
|
|
194
224
|
attr_writer :lines
|
@@ -200,29 +230,5 @@ module SRT
|
|
200
230
|
def errors
|
201
231
|
lines.collect { |l| l.error if l.error }.compact
|
202
232
|
end
|
203
|
-
|
204
|
-
protected
|
205
|
-
|
206
|
-
def self.parse_framerate(framerate_string)
|
207
|
-
mres = framerate_string.match(/(?<fps>\d+((\.)?\d+))(fps)/)
|
208
|
-
mres ? mres["fps"].to_f : nil
|
209
|
-
end
|
210
|
-
|
211
|
-
def self.parse_timecode(timecode_string)
|
212
|
-
mres = timecode_string.match(/(?<h>\d+):(?<m>\d+):(?<s>\d+),(?<mil>\d+)/)
|
213
|
-
mres ? "#{mres["h"].to_i * 3600 + mres["m"].to_i * 60 + mres["s"].to_i}.#{mres["mil"]}".to_f : nil
|
214
|
-
end
|
215
|
-
|
216
|
-
def self.parse_timespan(timespan_string)
|
217
|
-
factors = {
|
218
|
-
"mil" => 0.001,
|
219
|
-
"s" => 1,
|
220
|
-
"m" => 60,
|
221
|
-
"h" => 3600
|
222
|
-
}
|
223
|
-
|
224
|
-
mres = timespan_string.match(/(?<amount>(\+|-)?\d+((\.)?\d+))(?<unit>mil|s|m|h)/)
|
225
|
-
mres ? mres["amount"].to_f * factors[mres["unit"]] : nil
|
226
|
-
end
|
227
233
|
end
|
228
234
|
end
|
data/lib/srt/line.rb
CHANGED
@@ -32,8 +32,12 @@ module SRT
|
|
32
32
|
sequence.nil? && start_time.nil? && end_time.nil? && text.empty?
|
33
33
|
end
|
34
34
|
|
35
|
-
def time_str
|
36
|
-
[@start_time, @end_time].map { |t| sprintf("%02d:%02d:%02d
|
35
|
+
def time_str(subframe_separator=",")
|
36
|
+
[@start_time, @end_time].map { |t| sprintf("%02d:%02d:%02d#{subframe_separator}%s", t / 3600, (t % 3600) / 60, t % 60, sprintf("%.3f", t)[-3, 3]) }.join(" --> ")
|
37
|
+
end
|
38
|
+
|
39
|
+
def webvtt_time_str
|
40
|
+
time_str(".")
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
data/lib/srt/parser.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module SRT
|
2
|
+
class Parser
|
3
|
+
class << self
|
4
|
+
def framerate(framerate_string)
|
5
|
+
mres = framerate_string.match(/(?<fps>\d+((\.)?\d+))(fps)/)
|
6
|
+
mres ? mres["fps"].to_f : nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def id(id_string)
|
10
|
+
mres = id_string.match(/#(?<id>\d+)/)
|
11
|
+
mres ? mres["id"].to_i : nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def timecode(timecode_string)
|
15
|
+
mres = timecode_string.match(/(?<h>\d+):(?<m>\d+):(?<s>\d+),(?<ms>\d+)/)
|
16
|
+
mres ? "#{mres["h"].to_i * 3600 + mres["m"].to_i * 60 + mres["s"].to_i}.#{mres["ms"]}".to_f : nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def timespan(timespan_string)
|
20
|
+
factors = {
|
21
|
+
"ms" => 0.001,
|
22
|
+
"s" => 1,
|
23
|
+
"m" => 60,
|
24
|
+
"h" => 3600
|
25
|
+
}
|
26
|
+
mres = timespan_string.match(/(?<amount>(\+|-)?\d+((\.)?\d+)?)(?<unit>ms|s|m|h)/)
|
27
|
+
mres ? mres["amount"].to_f * factors[mres["unit"]] : nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/srt/version.rb
CHANGED
data/lib/srt.rb
CHANGED