srt 0.0.5 → 0.1.5
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 +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +7 -1
- data/README.md +39 -15
- data/lib/srt/file.rb +73 -66
- data/lib/srt/line.rb +12 -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 +446 -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 +54 -0
- data/spec/parser_spec.rb +42 -0
- data/spec/spec_helper.rb +2 -0
- data/srt.gemspec +2 -0
- metadata +50 -32
- data/spec/srt_spec.rb +0 -361
- /data/spec/{coordinates-dummy.srt → fixtures/coordinates-dummy.srt} +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 28e7323497b349d7a2089aa395ad2c1ba1c3985729111dc56706c3280521bb5f
|
4
|
+
data.tar.gz: 82fe89057aee1e6a6aea48a16d33740686a18d0444f5559820603ff1b9a2a062
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 699aaea2dda022acb10cb2e5d1db24d36ad8adec53528b1e186c6b75a549a049cbfd33f806d2cd4e276ae7ecabde412c9e9181c50335dc2f613ac79cefda5f31
|
7
|
+
data.tar.gz: c8abffc1bf997c624ee0cb0e06b0a49b9dd88b584a6fdb3c9edbd3170acb3a5e8a449b4d35eb3edfd2bc825c79389170da2d44589be602b02dced7a39223e4ef
|
data/.gitignore
CHANGED
data/.travis.yml
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,58 +16,58 @@ 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
|
67
67
|
srt_data.split(/\n/) + ["\n"]
|
68
68
|
rescue
|
69
69
|
begin
|
70
|
+
srt_data = srt_data.unpack("C*").pack("U*")
|
70
71
|
srt_data.force_encoding('utf-8').split(/\n/) + ["\n"]
|
71
72
|
rescue
|
72
73
|
srt_data.force_encoding('iso-8859-1').split(/\n/) + ["\n"]
|
@@ -75,8 +76,8 @@ module SRT
|
|
75
76
|
end
|
76
77
|
|
77
78
|
def append(options)
|
78
|
-
if options.length == 1 && options.values[0].class ==
|
79
|
-
reshift =
|
79
|
+
if options.length == 1 && options.values[0].class == self.class
|
80
|
+
reshift = Parser.timecode(options.keys[0]) || (lines.last.end_time + Parser.timespan(options.keys[0]))
|
80
81
|
renumber = lines.last.sequence
|
81
82
|
|
82
83
|
options.values[0].lines.each do |line|
|
@@ -91,10 +92,20 @@ module SRT
|
|
91
92
|
end
|
92
93
|
|
93
94
|
def split(options)
|
94
|
-
options = { :timeshift => true }.merge(options)
|
95
|
-
|
96
|
-
|
97
|
-
|
95
|
+
options = { :timeshift => true, :renumber => true }.merge(options)
|
96
|
+
|
97
|
+
split_points = []
|
98
|
+
|
99
|
+
if (options[:at])
|
100
|
+
split_points = [options[:at]].flatten.map{ |timecode| Parser.timecode(timecode) }.sort
|
101
|
+
elsif (options[:every])
|
102
|
+
interval = Parser.timecode(options[:every])
|
103
|
+
max = lines.last.end_time
|
104
|
+
(interval..max).step(interval){ |t| split_points << t }
|
105
|
+
end
|
106
|
+
|
107
|
+
if (split_points.count > 0)
|
108
|
+
split_offsprings = [File.new]
|
98
109
|
|
99
110
|
reshift = 0
|
100
111
|
renumber = 0
|
@@ -102,7 +113,7 @@ module SRT
|
|
102
113
|
lines.each do |line|
|
103
114
|
if split_points.empty? || line.end_time <= split_points.first
|
104
115
|
cloned_line = line.clone
|
105
|
-
cloned_line.sequence -= renumber
|
116
|
+
cloned_line.sequence -= renumber if options[:renumber]
|
106
117
|
if options[:timeshift]
|
107
118
|
cloned_line.start_time -= reshift
|
108
119
|
cloned_line.end_time -= reshift
|
@@ -110,7 +121,7 @@ module SRT
|
|
110
121
|
split_offsprings.last.lines << cloned_line
|
111
122
|
elsif line.start_time < split_points.first
|
112
123
|
cloned_line = line.clone
|
113
|
-
cloned_line.sequence -= renumber
|
124
|
+
cloned_line.sequence -= renumber if options[:renumber]
|
114
125
|
if options[:timeshift]
|
115
126
|
cloned_line.start_time -= reshift
|
116
127
|
cloned_line.end_time = split_points.first - reshift
|
@@ -121,9 +132,9 @@ module SRT
|
|
121
132
|
reshift = split_points.first
|
122
133
|
split_points.delete_at(0)
|
123
134
|
|
124
|
-
split_offsprings <<
|
135
|
+
split_offsprings << File.new
|
125
136
|
cloned_line = line.clone
|
126
|
-
cloned_line.sequence -= renumber
|
137
|
+
cloned_line.sequence -= renumber if options[:renumber]
|
127
138
|
if options[:timeshift]
|
128
139
|
cloned_line.start_time = 0
|
129
140
|
cloned_line.end_time -= reshift
|
@@ -134,9 +145,9 @@ module SRT
|
|
134
145
|
reshift = split_points.first
|
135
146
|
split_points.delete_at(0)
|
136
147
|
|
137
|
-
split_offsprings <<
|
148
|
+
split_offsprings << File.new
|
138
149
|
cloned_line = line.clone
|
139
|
-
cloned_line.sequence -= renumber
|
150
|
+
cloned_line.sequence -= renumber if options[:renumber]
|
140
151
|
if options[:timeshift]
|
141
152
|
cloned_line.start_time -= reshift
|
142
153
|
cloned_line.end_time -= reshift
|
@@ -151,26 +162,37 @@ module SRT
|
|
151
162
|
|
152
163
|
def timeshift(options)
|
153
164
|
if options.length == 1
|
154
|
-
if options[:all] && (seconds =
|
165
|
+
if options[:all] && (seconds = Parser.timespan(options[:all]))
|
155
166
|
lines.each do |line|
|
156
167
|
line.start_time += seconds
|
157
168
|
line.end_time += seconds
|
158
169
|
end
|
159
|
-
elsif (original_framerate =
|
160
|
-
|
170
|
+
elsif (original_framerate = Parser.framerate(options.keys[0])) && (target_framerate = Parser.framerate(options.values[0]))
|
171
|
+
time_ratio = original_framerate / target_framerate
|
161
172
|
lines.each do |line|
|
162
|
-
line.start_time *=
|
163
|
-
line.end_time *=
|
173
|
+
line.start_time *= time_ratio
|
174
|
+
line.end_time *= time_ratio
|
164
175
|
end
|
165
176
|
end
|
166
177
|
elsif options.length == 2
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
178
|
+
origins, targets = options.keys, options.values
|
179
|
+
|
180
|
+
[0,1].each do |i|
|
181
|
+
if origins[i].is_a?(String) && Parser.id(origins[i])
|
182
|
+
origins[i] = lines[Parser.id(origins[i]) - 1].start_time
|
183
|
+
elsif origins[i].is_a?(String) && Parser.timecode(origins[i])
|
184
|
+
origins[i] = Parser.timecode(origins[i])
|
185
|
+
end
|
186
|
+
|
187
|
+
if targets[i].is_a?(String) && Parser.timecode(targets[i])
|
188
|
+
targets[i] = Parser.timecode(targets[i])
|
189
|
+
elsif targets[i].is_a?(String) && Parser.timespan(targets[i])
|
190
|
+
targets[i] = origins[i] + Parser.timespan(targets[i])
|
191
|
+
end
|
192
|
+
end
|
171
193
|
|
172
|
-
time_rescale_factor = (
|
173
|
-
time_rebase_shift =
|
194
|
+
time_rescale_factor = (targets[1] - targets[0]) / (origins[1] - origins[0])
|
195
|
+
time_rebase_shift = targets[0] - origins[0] * time_rescale_factor
|
174
196
|
|
175
197
|
lines.each do |line|
|
176
198
|
line.start_time = line.start_time * time_rescale_factor + time_rebase_shift
|
@@ -187,8 +209,17 @@ module SRT
|
|
187
209
|
end
|
188
210
|
end
|
189
211
|
|
190
|
-
def to_s
|
191
|
-
lines.map { |l|
|
212
|
+
def to_s(time_str_function=:time_str)
|
213
|
+
lines.map { |l| l.to_s(time_str_function) }.join("\n")
|
214
|
+
end
|
215
|
+
|
216
|
+
def to_webvtt
|
217
|
+
header = <<eos
|
218
|
+
WEBVTT
|
219
|
+
X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000
|
220
|
+
|
221
|
+
eos
|
222
|
+
header + to_s(:webvtt_time_str)
|
192
223
|
end
|
193
224
|
|
194
225
|
attr_writer :lines
|
@@ -200,29 +231,5 @@ module SRT
|
|
200
231
|
def errors
|
201
232
|
lines.collect { |l| l.error if l.error }.compact
|
202
233
|
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
234
|
end
|
228
235
|
end
|
data/lib/srt/line.rb
CHANGED
@@ -32,8 +32,18 @@ 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|
|
35
|
+
def time_str(subframe_separator=",")
|
36
|
+
[@start_time, @end_time].map { |t| f=sprintf("%.3f", t); ip=f[0,f.size-4].to_i;fp=f[-3,3]; "%02d:%02d:%02d#{subframe_separator}%s" % [ip / 3600, (ip % 3600) / 60, ip % 60,fp] }.join(" --> ")
|
37
|
+
end
|
38
|
+
|
39
|
+
def webvtt_time_str
|
40
|
+
time_str(".")
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s(time_str_function=:time_str)
|
44
|
+
content = text.empty? ? [''] : text
|
45
|
+
coordinates = display_coordinates ? display_coordinates : ""
|
46
|
+
[sequence, send(time_str_function) + coordinates, content, ""].flatten.join("\n")
|
37
47
|
end
|
38
48
|
end
|
39
49
|
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